From 530e51d56800ce943a599524e8b9104f7e0bae31 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 11 Oct 2025 22:00:07 -0400 Subject: [PATCH 01/11] feat: add generative creative support to sync_creatives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables AI-generated creatives directly through sync_creatives by treating input types (static assets, generation prompts, webhooks) as different formats. Key changes: - Add brand_card and url as asset types - Update creative-asset schema to use format_id and assets object structure - Add inputs array for preview contexts - Add previews array to sync_creatives response - Create sync-creatives-approval-request schema for conversational workflow - Add format discoverability fields (description, preview_image, example_url) - Update tests to reflect new creative-asset required fields Benefits: - Simpler workflow: one task instead of build → preview → sync - Format clarity: input method explicit in format_id - Unified model: same structure for static, generative, and dynamic creatives 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- GENERATIVE_CREATIVES_SPEC.md | 298 ++++++++++++++++++ static/schemas/v1/core/asset-type.json | 2 +- static/schemas/v1/core/creative-asset.json | 153 +++++---- static/schemas/v1/core/format.json | 18 +- .../sync-creatives-approval-request.json | 115 +++++++ .../v1/media-buy/sync-creatives-response.json | 47 +++ tests/schema-validation.test.js | 2 +- 7 files changed, 570 insertions(+), 65 deletions(-) create mode 100644 GENERATIVE_CREATIVES_SPEC.md create mode 100644 static/schemas/v1/media-buy/sync-creatives-approval-request.json diff --git a/GENERATIVE_CREATIVES_SPEC.md b/GENERATIVE_CREATIVES_SPEC.md new file mode 100644 index 0000000000..0471c51054 --- /dev/null +++ b/GENERATIVE_CREATIVES_SPEC.md @@ -0,0 +1,298 @@ +# Generative Creatives in sync_creatives + +## Overview + +This spec adds support for AI-generated creatives directly through `sync_creatives`, eliminating the need for separate `build_creative` and `preview_creative` tasks. Formats themselves define what inputs they accept (static assets, generation prompts, webhooks, etc.). + +## Key Design Principles + +1. **Input type = Format type** - Different formats for different input methods (static, generative, webhook, feed) +2. **Everything is an asset** - No distinction between "fields" and "assets"; brand_card and text prompts are asset types +3. **Previews in response** - Generative creatives return preview URLs directly in sync_creatives response +4. **Conversational approval** - Use context_id to approve or refine creatives + +## Schema Changes + +### New Asset Types + +Added to asset type enums: +- `brand_card` - Brand context object with URL, colors, fonts, tone +- `url` - URL asset type (was missing) + +### Updated Schemas + +1. **`/schemas/v1/core/asset-type.json`** + - Added `brand_card` and `url` to type enum + +2. **`/schemas/v1/core/creative-asset.json`** + - Changed `format` → `format_id` + - Replaced flat structure with `assets` object (keyed by asset_role) + - Added `inputs` array for preview contexts + - Each asset has `asset_type` and type-specific properties + +3. **`/schemas/v1/core/format.json`** + - Added `description`, `preview_image`, `example_url` for discoverability + - Added `brand_card` to asset_type enums + +4. **`/schemas/v1/media-buy/sync-creatives-response.json`** + - Added `previews` array to results + - Added `interactive_url` and `expires_at` for preview management + +5. **`/schemas/v1/media-buy/sync-creatives-approval-request.json`** (NEW) + - Schema for approving or refining generative creatives + - Uses `context_id` to continue conversation + - `approve` array for creative_ids to save + - `refine` array for creatives to regenerate with new prompts + +## Workflow Examples + +### Example 1: Static Asset Upload + +```json +{ + "creatives": [ + { + "creative_id": "banner_001", + "format_id": "display_300x250_static", + "name": "Summer Sale Banner", + "assets": { + "brand_context": { + "asset_type": "brand_card", + "url": "https://example.com" + }, + "banner_image": { + "asset_type": "image", + "url": "https://cdn.example.com/banner.jpg", + "width": 300, + "height": 250 + } + } + } + ] +} +``` + +Response: `status: "completed"` (immediate) + +### Example 2: Generative Creative + +```json +{ + "creatives": [ + { + "creative_id": "banner_002", + "format_id": "display_300x250_generative", + "name": "AI Generated Banner", + "assets": { + "brand_context": { + "asset_type": "brand_card", + "url": "https://example.com", + "colors": { + "primary": "#FF6B35", + "secondary": "#004E89" + } + }, + "generation_prompt": { + "asset_type": "text", + "content": "Create a summer sale banner with beach vibes" + } + }, + "inputs": [ + {"name": "Desktop", "macros": {"DEVICE_TYPE": "desktop"}}, + {"name": "Mobile", "macros": {"DEVICE_TYPE": "mobile"}} + ] + } + ] +} +``` + +Response: +```json +{ + "status": "completed", + "context_id": "ctx_abc123", + "results": [ + { + "creative_id": "banner_002", + "action": "generated", + "status": "pending_review", + "previews": [ + { + "preview_url": "https://pub.com/preview/abc/desktop", + "input": { + "name": "Desktop", + "macros": {"DEVICE_TYPE": "desktop"} + } + }, + { + "preview_url": "https://pub.com/preview/abc/mobile", + "input": { + "name": "Mobile", + "macros": {"DEVICE_TYPE": "mobile"} + } + } + ], + "interactive_url": "https://pub.com/preview/abc/interactive", + "expires_at": "2025-10-12T10:00:00Z" + } + ] +} +``` + +### Example 3: Approve Preview + +```json +{ + "context_id": "ctx_abc123", + "approve": ["banner_002"] +} +``` + +Response: +```json +{ + "status": "completed", + "results": [ + { + "creative_id": "banner_002", + "action": "created", + "status": "approved", + "platform_id": "plt_456" + } + ] +} +``` + +### Example 4: Refine and Regenerate + +```json +{ + "context_id": "ctx_abc123", + "refine": [ + { + "creative_id": "banner_002", + "assets": { + "generation_prompt": { + "asset_type": "text", + "content": "Make it more vibrant with a stronger CTA button" + } + } + } + ] +} +``` + +Response: New previews with updated creative + +## Format Types + +Publishers can offer different format types based on input methods: + +### Static Formats +```json +{ + "format_id": "display_300x250_static", + "name": "Display Banner 300x250 - Static Image", + "description": "Upload your pre-made 300x250 banner image", + "type": "display", + "assets_required": [ + { + "asset_id": "brand_context", + "asset_type": "brand_card", + "required": true + }, + { + "asset_id": "banner_image", + "asset_type": "image", + "required": true, + "requirements": { + "dimensions": {"width": 300, "height": 250} + } + } + ] +} +``` + +### Generative Formats +```json +{ + "format_id": "display_300x250_generative", + "name": "Display Banner 300x250 - AI Generated", + "description": "Generate custom banners using AI from your brand card and prompt", + "type": "display", + "preview_image": "https://pub.com/format-previews/display_300x250_gen.png", + "example_url": "https://pub.com/format-showcase/generative-display", + "assets_required": [ + { + "asset_id": "brand_context", + "asset_type": "brand_card", + "required": true + }, + { + "asset_id": "generation_prompt", + "asset_type": "text", + "required": true + } + ] +} +``` + +### Dynamic Generative Formats +```json +{ + "format_id": "audio_host_read_30s_dynamic", + "name": "Dynamic AI Host Read 30s", + "description": "AI-generated host reads that adapt to podcast context in real-time", + "type": "audio", + "assets_required": [ + { + "asset_id": "brand_context", + "asset_type": "brand_card", + "required": true + }, + { + "asset_id": "generation_prompt", + "asset_type": "text", + "required": true + } + ] +} +``` + +Note: Dynamic formats generate content per-impression. When approved, they save the generation instructions, not a static asset. + +## Implementation Notes + +### For Publishers + +1. **Format Discovery**: Expose format capabilities through `list_creative_formats` +2. **Creative Agent Integration**: + - Receive sync_creatives with generative format + - Call internal/external creative agent + - Return preview URLs in response +3. **Preview Management**: Host preview URLs that render HTML pages +4. **Approval Flow**: Handle approval/refine requests using context_id + +### For Buyers + +1. **Format Selection**: Browse available formats, filter by capabilities +2. **Asset Preparation**: Provide assets matching format requirements +3. **Preview Review**: Review generated previews before approval +4. **Conversational Refinement**: Iterate with new prompts until satisfied + +## Migration Path + +### Deprecated Tasks +- `build_creative` - Use `sync_creatives` with generative format instead +- `preview_creative` - Previews now included in sync_creatives response + +### Backward Compatibility +Old creative-asset schema (with `format`, `media_url`, `snippet`) should continue to work during transition period, but new implementations should use the asset-based model. + +## Benefits + +1. **Simpler workflow** - One task instead of three (build → preview → sync) +2. **Format clarity** - Input method is explicit in format_id +3. **Publisher flexibility** - Each publisher decides which input methods to support +4. **Unified model** - Same structure for static, generative, and dynamic creatives +5. **Better discoverability** - Formats are self-documenting with descriptions and examples diff --git a/static/schemas/v1/core/asset-type.json b/static/schemas/v1/core/asset-type.json index 567f9d68e0..a27cf1c89d 100644 --- a/static/schemas/v1/core/asset-type.json +++ b/static/schemas/v1/core/asset-type.json @@ -11,7 +11,7 @@ }, "type": { "type": "string", - "enum": ["image", "video", "audio", "text", "html", "css", "javascript"], + "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "brand_card", "url"], "description": "Type of asset" }, "required": { diff --git a/static/schemas/v1/core/creative-asset.json b/static/schemas/v1/core/creative-asset.json index df2b828a8f..a4c932f985 100644 --- a/static/schemas/v1/core/creative-asset.json +++ b/static/schemas/v1/core/creative-asset.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/v1/core/creative-asset.json", "title": "Creative Asset", - "description": "Creative asset for upload to library - supports both hosted assets and third-party snippets", + "description": "Creative asset for upload to library - supports static assets, generative formats, and third-party snippets", "type": "object", "properties": { "creative_id": { @@ -13,77 +13,108 @@ "type": "string", "description": "Human-readable creative name" }, - "format": { + "format_id": { "type": "string", - "description": "Creative format type (e.g., video, audio, display)" + "description": "Format identifier (e.g., display_300x250_static, display_300x250_generative)" }, - "media_url": { - "type": "string", - "format": "uri", - "description": "URL of the creative file (for hosted assets)" - }, - "snippet": { - "type": "string", - "description": "Third-party tag, VAST XML, or code snippet (for third-party served assets)" - }, - "snippet_type": { - "$ref": "/schemas/v1/enums/snippet-type.json", - "description": "Type of snippet content" - }, - "click_url": { - "type": "string", - "format": "uri", - "description": "Landing page URL for the creative" - }, - "duration": { - "type": "number", - "description": "Duration in milliseconds (for video/audio)", - "minimum": 0 - }, - "width": { - "type": "number", - "description": "Width in pixels (for video/display)", - "minimum": 0 - }, - "height": { - "type": "number", - "description": "Height in pixels (for video/display)", - "minimum": 0 + "assets": { + "type": "object", + "description": "Assets required by the format, keyed by asset_role", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "object", + "description": "Asset definition", + "properties": { + "asset_type": { + "type": "string", + "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "brand_card", "url"], + "description": "Type of this asset" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL for image/video/audio assets or brand_card URL" + }, + "content": { + "type": "string", + "description": "Content for text/html/css/javascript assets" + }, + "width": { + "type": "integer", + "description": "Width in pixels (for images/video)", + "minimum": 1 + }, + "height": { + "type": "integer", + "description": "Height in pixels (for images/video)", + "minimum": 1 + }, + "duration_ms": { + "type": "integer", + "description": "Duration in milliseconds (for video/audio)", + "minimum": 0 + }, + "format": { + "type": "string", + "description": "File format (jpg, png, mp4, etc.)" + }, + "colors": { + "type": "object", + "description": "Brand colors (for brand_card asset type)", + "properties": { + "primary": {"type": "string"}, + "secondary": {"type": "string"}, + "accent": {"type": "string"} + } + }, + "fonts": { + "type": "array", + "description": "Brand fonts (for brand_card asset type)", + "items": {"type": "string"} + }, + "tone": { + "type": "string", + "description": "Brand tone/voice (for brand_card asset type)" + } + }, + "required": ["asset_type"], + "additionalProperties": true + } + }, + "additionalProperties": false }, - "tags": { + "inputs": { "type": "array", - "description": "User-defined tags for organization and searchability", + "description": "Preview contexts for generative formats - defines what scenarios to generate previews for", "items": { - "type": "string" + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name for this preview variant" + }, + "macros": { + "type": "object", + "description": "Macro values to apply for this preview", + "additionalProperties": {"type": "string"} + }, + "context_description": { + "type": "string", + "description": "Natural language description of the context for AI-generated content" + } + }, + "required": ["name"], + "additionalProperties": false } }, - "assets": { + "tags": { "type": "array", - "description": "Sub-assets for multi-asset formats like carousels", + "description": "User-defined tags for organization and searchability", "items": { - "$ref": "/schemas/v1/core/sub-asset.json" + "type": "string" } } }, - "required": ["creative_id", "name", "format"], - "oneOf": [ - { - "description": "Hosted asset - requires media_url", - "required": ["media_url"], - "not": { - "anyOf": [ - { "required": ["snippet"] }, - { "required": ["snippet_type"] } - ] - } - }, - { - "description": "Third-party asset - requires snippet and snippet_type", - "required": ["snippet", "snippet_type"], - "not": { - "required": ["media_url"] - } - } - ], + "required": ["creative_id", "name", "format_id", "assets"], "additionalProperties": false } \ No newline at end of file diff --git a/static/schemas/v1/core/format.json b/static/schemas/v1/core/format.json index ed8c802776..c7ca6ca4ee 100644 --- a/static/schemas/v1/core/format.json +++ b/static/schemas/v1/core/format.json @@ -18,6 +18,20 @@ "type": "string", "description": "Human-readable format name" }, + "description": { + "type": "string", + "description": "Plain text explanation of what this format does and what assets it requires" + }, + "preview_image": { + "type": "string", + "format": "uri", + "description": "Optional preview image URL for format browsing/discovery UI" + }, + "example_url": { + "type": "string", + "format": "uri", + "description": "Optional URL to showcase page with examples and interactive demos of this format" + }, "type": { "type": "string", "description": "Media type of this format - determines rendering method and asset requirements", @@ -53,7 +67,7 @@ "asset_type": { "type": "string", "description": "Type of asset", - "enum": ["image", "video", "audio", "text", "html", "javascript", "url"] + "enum": ["image", "video", "audio", "text", "html", "javascript", "url", "brand_card"] }, "asset_role": { "type": "string", @@ -107,7 +121,7 @@ "asset_type": { "type": "string", "description": "Type of asset", - "enum": ["image", "video", "audio", "text", "html", "javascript", "url"] + "enum": ["image", "video", "audio", "text", "html", "javascript", "url", "brand_card"] }, "asset_role": { "type": "string", diff --git a/static/schemas/v1/media-buy/sync-creatives-approval-request.json b/static/schemas/v1/media-buy/sync-creatives-approval-request.json new file mode 100644 index 0000000000..0ba76d6b60 --- /dev/null +++ b/static/schemas/v1/media-buy/sync-creatives-approval-request.json @@ -0,0 +1,115 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/sync-creatives-approval-request.json", + "title": "Sync Creatives Approval Request", + "description": "Approve or refine generative creatives after reviewing previews. Uses context_id from initial sync_creatives response to continue the conversation.", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.0" + }, + "context_id": { + "type": "string", + "description": "Context ID from the initial sync_creatives response that returned previews" + }, + "approve": { + "type": "array", + "description": "Array of creative_ids to approve and save to library", + "items": { + "type": "string" + } + }, + "refine": { + "type": "array", + "description": "Array of creatives to regenerate with new instructions", + "items": { + "type": "object", + "properties": { + "creative_id": { + "type": "string", + "description": "Creative ID to refine" + }, + "assets": { + "type": "object", + "description": "Updated assets (typically just generation_prompt with new message)", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "enum": ["text", "brand_card"] + }, + "content": { + "type": "string", + "description": "New message/prompt for regeneration" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Updated brand card URL" + } + }, + "required": ["asset_type"] + } + } + }, + "inputs": { + "type": "array", + "description": "Optional: New preview contexts to test with refined creative", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "macros": { + "type": "object", + "additionalProperties": {"type": "string"} + }, + "context_description": {"type": "string"} + }, + "required": ["name"] + } + } + }, + "required": ["creative_id", "assets"], + "additionalProperties": false + } + } + }, + "required": ["context_id"], + "anyOf": [ + {"required": ["approve"]}, + {"required": ["refine"]} + ], + "additionalProperties": false, + "examples": [ + { + "description": "Approve one creative, refine another", + "data": { + "context_id": "ctx_abc123", + "approve": ["banner_001"], + "refine": [ + { + "creative_id": "banner_002", + "assets": { + "generation_prompt": { + "asset_type": "text", + "content": "Make it more vibrant with a stronger CTA" + } + } + } + ] + } + }, + { + "description": "Approve all creatives", + "data": { + "context_id": "ctx_abc123", + "approve": ["banner_001", "banner_002", "banner_003"] + } + } + ] +} diff --git a/static/schemas/v1/media-buy/sync-creatives-response.json b/static/schemas/v1/media-buy/sync-creatives-response.json index 4fd1b4a8d9..cbb75ba678 100644 --- a/static/schemas/v1/media-buy/sync-creatives-response.json +++ b/static/schemas/v1/media-buy/sync-creatives-response.json @@ -147,6 +147,53 @@ "required": ["adaptation_id", "format_id", "name", "description"], "additionalProperties": false } + }, + "previews": { + "type": "array", + "description": "Preview URLs for generative creatives - one preview per input provided in request", + "items": { + "type": "object", + "properties": { + "preview_url": { + "type": "string", + "format": "uri", + "description": "URL to HTML page that renders this preview variant" + }, + "input": { + "type": "object", + "description": "Echo of input parameters used to generate this preview", + "properties": { + "name": { + "type": "string", + "description": "Name of this preview variant" + }, + "macros": { + "type": "object", + "description": "Macro values applied", + "additionalProperties": {"type": "string"} + }, + "context_description": { + "type": "string", + "description": "Context description applied" + } + }, + "required": ["name"], + "additionalProperties": false + } + }, + "required": ["preview_url", "input"], + "additionalProperties": false + } + }, + "interactive_url": { + "type": "string", + "format": "uri", + "description": "Optional URL to interactive testing page with controls to switch between preview variants" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when preview links expire" } }, "required": ["creative_id", "action"], diff --git a/tests/schema-validation.test.js b/tests/schema-validation.test.js index 53707951eb..cc209a37a4 100644 --- a/tests/schema-validation.test.js +++ b/tests/schema-validation.test.js @@ -275,7 +275,7 @@ async function runTests() { 'product.json': ['product_id', 'name', 'description', 'format_ids', 'delivery_type', 'is_fixed_price'], 'media-buy.json': ['media_buy_id', 'status', 'promoted_offering', 'total_budget', 'packages'], 'package.json': ['package_id', 'status'], - 'creative-asset.json': ['creative_id', 'name', 'format'], + 'creative-asset.json': ['creative_id', 'name', 'format_id', 'assets'], 'error.json': ['code', 'message'], 'budget.json': ['total', 'currency'] }; From 885b84d308ae4971bc8a50ee3d72cb6596d9a2e0 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 08:01:38 -0400 Subject: [PATCH 02/11] refactor: rename brand_card to brand_manifest throughout codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename brand_card to brand_manifest for consistency and clarity. The term "manifest" better reflects its role as a comprehensive brand information document. Changes: - Renamed brand-card.json to brand-manifest.json - Updated all schema references from brand_card to brand_manifest - Updated all documentation references - Deleted GENERATIVE_CREATIVES_SPEC.md (not needed in repo) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- GENERATIVE_CREATIVES_SPEC.md | 298 ------------------ docs/creative/generative-creative.md | 20 +- .../creative/task-reference/build_creative.md | 12 +- .../task-reference/preview_creative.md | 8 +- docs/media-buy/creatives/index.md | 4 +- .../task-reference/create_media_buy.md | 6 +- .../{brand-card.md => brand-manifest.md} | 42 +-- static/schemas/v1/core/asset-type.json | 2 +- .../{brand-card.json => brand-manifest.json} | 8 +- static/schemas/v1/core/creative-asset.json | 10 +- static/schemas/v1/core/format.json | 4 +- static/schemas/v1/core/promoted-products.json | 2 +- .../v1/creative/preview-creative-request.json | 6 +- static/schemas/v1/index.json | 4 +- .../v1/media-buy/build-creative-request.json | 10 +- .../media-buy/create-media-buy-request.json | 10 +- .../sync-creatives-approval-request.json | 4 +- 17 files changed, 76 insertions(+), 374 deletions(-) delete mode 100644 GENERATIVE_CREATIVES_SPEC.md rename docs/reference/{brand-card.md => brand-manifest.md} (86%) rename static/schemas/v1/core/{brand-card.json => brand-manifest.json} (98%) diff --git a/GENERATIVE_CREATIVES_SPEC.md b/GENERATIVE_CREATIVES_SPEC.md deleted file mode 100644 index 0471c51054..0000000000 --- a/GENERATIVE_CREATIVES_SPEC.md +++ /dev/null @@ -1,298 +0,0 @@ -# Generative Creatives in sync_creatives - -## Overview - -This spec adds support for AI-generated creatives directly through `sync_creatives`, eliminating the need for separate `build_creative` and `preview_creative` tasks. Formats themselves define what inputs they accept (static assets, generation prompts, webhooks, etc.). - -## Key Design Principles - -1. **Input type = Format type** - Different formats for different input methods (static, generative, webhook, feed) -2. **Everything is an asset** - No distinction between "fields" and "assets"; brand_card and text prompts are asset types -3. **Previews in response** - Generative creatives return preview URLs directly in sync_creatives response -4. **Conversational approval** - Use context_id to approve or refine creatives - -## Schema Changes - -### New Asset Types - -Added to asset type enums: -- `brand_card` - Brand context object with URL, colors, fonts, tone -- `url` - URL asset type (was missing) - -### Updated Schemas - -1. **`/schemas/v1/core/asset-type.json`** - - Added `brand_card` and `url` to type enum - -2. **`/schemas/v1/core/creative-asset.json`** - - Changed `format` → `format_id` - - Replaced flat structure with `assets` object (keyed by asset_role) - - Added `inputs` array for preview contexts - - Each asset has `asset_type` and type-specific properties - -3. **`/schemas/v1/core/format.json`** - - Added `description`, `preview_image`, `example_url` for discoverability - - Added `brand_card` to asset_type enums - -4. **`/schemas/v1/media-buy/sync-creatives-response.json`** - - Added `previews` array to results - - Added `interactive_url` and `expires_at` for preview management - -5. **`/schemas/v1/media-buy/sync-creatives-approval-request.json`** (NEW) - - Schema for approving or refining generative creatives - - Uses `context_id` to continue conversation - - `approve` array for creative_ids to save - - `refine` array for creatives to regenerate with new prompts - -## Workflow Examples - -### Example 1: Static Asset Upload - -```json -{ - "creatives": [ - { - "creative_id": "banner_001", - "format_id": "display_300x250_static", - "name": "Summer Sale Banner", - "assets": { - "brand_context": { - "asset_type": "brand_card", - "url": "https://example.com" - }, - "banner_image": { - "asset_type": "image", - "url": "https://cdn.example.com/banner.jpg", - "width": 300, - "height": 250 - } - } - } - ] -} -``` - -Response: `status: "completed"` (immediate) - -### Example 2: Generative Creative - -```json -{ - "creatives": [ - { - "creative_id": "banner_002", - "format_id": "display_300x250_generative", - "name": "AI Generated Banner", - "assets": { - "brand_context": { - "asset_type": "brand_card", - "url": "https://example.com", - "colors": { - "primary": "#FF6B35", - "secondary": "#004E89" - } - }, - "generation_prompt": { - "asset_type": "text", - "content": "Create a summer sale banner with beach vibes" - } - }, - "inputs": [ - {"name": "Desktop", "macros": {"DEVICE_TYPE": "desktop"}}, - {"name": "Mobile", "macros": {"DEVICE_TYPE": "mobile"}} - ] - } - ] -} -``` - -Response: -```json -{ - "status": "completed", - "context_id": "ctx_abc123", - "results": [ - { - "creative_id": "banner_002", - "action": "generated", - "status": "pending_review", - "previews": [ - { - "preview_url": "https://pub.com/preview/abc/desktop", - "input": { - "name": "Desktop", - "macros": {"DEVICE_TYPE": "desktop"} - } - }, - { - "preview_url": "https://pub.com/preview/abc/mobile", - "input": { - "name": "Mobile", - "macros": {"DEVICE_TYPE": "mobile"} - } - } - ], - "interactive_url": "https://pub.com/preview/abc/interactive", - "expires_at": "2025-10-12T10:00:00Z" - } - ] -} -``` - -### Example 3: Approve Preview - -```json -{ - "context_id": "ctx_abc123", - "approve": ["banner_002"] -} -``` - -Response: -```json -{ - "status": "completed", - "results": [ - { - "creative_id": "banner_002", - "action": "created", - "status": "approved", - "platform_id": "plt_456" - } - ] -} -``` - -### Example 4: Refine and Regenerate - -```json -{ - "context_id": "ctx_abc123", - "refine": [ - { - "creative_id": "banner_002", - "assets": { - "generation_prompt": { - "asset_type": "text", - "content": "Make it more vibrant with a stronger CTA button" - } - } - } - ] -} -``` - -Response: New previews with updated creative - -## Format Types - -Publishers can offer different format types based on input methods: - -### Static Formats -```json -{ - "format_id": "display_300x250_static", - "name": "Display Banner 300x250 - Static Image", - "description": "Upload your pre-made 300x250 banner image", - "type": "display", - "assets_required": [ - { - "asset_id": "brand_context", - "asset_type": "brand_card", - "required": true - }, - { - "asset_id": "banner_image", - "asset_type": "image", - "required": true, - "requirements": { - "dimensions": {"width": 300, "height": 250} - } - } - ] -} -``` - -### Generative Formats -```json -{ - "format_id": "display_300x250_generative", - "name": "Display Banner 300x250 - AI Generated", - "description": "Generate custom banners using AI from your brand card and prompt", - "type": "display", - "preview_image": "https://pub.com/format-previews/display_300x250_gen.png", - "example_url": "https://pub.com/format-showcase/generative-display", - "assets_required": [ - { - "asset_id": "brand_context", - "asset_type": "brand_card", - "required": true - }, - { - "asset_id": "generation_prompt", - "asset_type": "text", - "required": true - } - ] -} -``` - -### Dynamic Generative Formats -```json -{ - "format_id": "audio_host_read_30s_dynamic", - "name": "Dynamic AI Host Read 30s", - "description": "AI-generated host reads that adapt to podcast context in real-time", - "type": "audio", - "assets_required": [ - { - "asset_id": "brand_context", - "asset_type": "brand_card", - "required": true - }, - { - "asset_id": "generation_prompt", - "asset_type": "text", - "required": true - } - ] -} -``` - -Note: Dynamic formats generate content per-impression. When approved, they save the generation instructions, not a static asset. - -## Implementation Notes - -### For Publishers - -1. **Format Discovery**: Expose format capabilities through `list_creative_formats` -2. **Creative Agent Integration**: - - Receive sync_creatives with generative format - - Call internal/external creative agent - - Return preview URLs in response -3. **Preview Management**: Host preview URLs that render HTML pages -4. **Approval Flow**: Handle approval/refine requests using context_id - -### For Buyers - -1. **Format Selection**: Browse available formats, filter by capabilities -2. **Asset Preparation**: Provide assets matching format requirements -3. **Preview Review**: Review generated previews before approval -4. **Conversational Refinement**: Iterate with new prompts until satisfied - -## Migration Path - -### Deprecated Tasks -- `build_creative` - Use `sync_creatives` with generative format instead -- `preview_creative` - Previews now included in sync_creatives response - -### Backward Compatibility -Old creative-asset schema (with `format`, `media_url`, `snippet`) should continue to work during transition period, but new implementations should use the asset-based model. - -## Benefits - -1. **Simpler workflow** - One task instead of three (build → preview → sync) -2. **Format clarity** - Input method is explicit in format_id -3. **Publisher flexibility** - Each publisher decides which input methods to support -4. **Unified model** - Same structure for static, generative, and dynamic creatives -5. **Better discoverability** - Formats are self-documenting with descriptions and examples diff --git a/docs/creative/generative-creative.md b/docs/creative/generative-creative.md index 211c9a6520..cbc387bf62 100644 --- a/docs/creative/generative-creative.md +++ b/docs/creative/generative-creative.md @@ -12,7 +12,7 @@ The Creative Protocol provides AI-powered creative generation: - **`preview_creative`**: Generate previews of creative manifests - **`list_creative_formats`**: Discover supported creative formats -Assets are provided via [Brand Card](../reference/brand-card) - no separate asset library management needed. +Assets are provided via [Brand Manifest](../reference/brand-manifest) - no separate asset library management needed. ## Quick Start: Generate Your First Creative @@ -81,7 +81,7 @@ Use the `context_id` to make improvements: ## Common Patterns -### Using Brand Cards +### Using Brand Manifests Provide brand context for better creative generation: @@ -89,7 +89,7 @@ Provide brand context for better creative generation: { "message": "Create a display ad for our coffee shop promotion", "format_id": "display_300x250", - "brand_card": { + "brand_manifest": { "url": "https://mycoffeeshop.com", "name": "Brew & Co", "colors": { @@ -102,19 +102,19 @@ Provide brand context for better creative generation: } ``` -**Minimal Brand Card**: Start with just a URL for low-friction creative generation: +**Minimal Brand Manifest**: Start with just a URL for low-friction creative generation: ```json { "message": "Create a coffee shop ad", "format_id": "display_native", - "brand_card": { + "brand_manifest": { "url": "https://mycoffeeshop.com" } } ``` -See [Brand Card Reference](../reference/brand-card) for comprehensive examples. +See [Brand Manifest Reference](../reference/brand-manifest) for comprehensive examples. ### Using Your Own Assets @@ -124,7 +124,7 @@ Provide existing assets to incorporate into the creative: { "message": "Create a display ad featuring our signature latte", "format_id": "display_300x250", - "brand_card": { + "brand_manifest": { "url": "https://mycoffeeshop.com" }, "assets": [ @@ -191,12 +191,12 @@ If you get a format error, the publisher may not support that format. Try: ### Creative Quality Issues To improve creative output: 1. Be more specific in your message: "Create a minimalist coffee ad with earth tones" -2. Provide comprehensive brand card with assets and guidelines +2. Provide comprehensive brand manifest with assets and guidelines 3. Use the conversational refinement feature to iterate (via `context_id`) ### Asset Management -Assets are provided via [Brand Card](../reference/brand-card): -1. Include assets in brand card with descriptive tags +Assets are provided via [Brand Manifest](../reference/brand-manifest): +1. Include assets in brand manifest with descriptive tags 2. Use `asset_filters` in requests to select specific assets 3. Reference product catalogs for large inventories diff --git a/docs/creative/task-reference/build_creative.md b/docs/creative/task-reference/build_creative.md index 5706e77cb3..1f380d17c0 100644 --- a/docs/creative/task-reference/build_creative.md +++ b/docs/creative/task-reference/build_creative.md @@ -41,7 +41,7 @@ Creative agents need to understand format requirements to generate appropriate c | `format_source` | string | No | Source URL for format lookup (sales agent URL). If null/omitted, assumes standard AdCP format | | `format_id` | string | Yes | Format identifier to look up from the format source | | `context_id` | string | No | Session context from previous message for continuity | -| `brand_card` | BrandCard | No | Brand information manifest for creative generation context. See [Brand Card](../../reference/brand-card) for details. Replaces legacy `brand_guidelines`. | +| `brand_manifest` | BrandCard | No | Brand information manifest for creative generation context. See [Brand Manifest](../../reference/brand-manifest) for details. Replaces legacy `brand_guidelines`. | | `assets` | array | No | References to asset libraries and specific assets | | `output_mode` | string | No | `"manifest"` for creative manifest or `"code"` for executable (default: `"manifest"`) | | `preview_options` | object | No | Options for generating preview | @@ -175,15 +175,15 @@ Creative agents need to understand format requirements to generate appropriate c ## Examples -### Example 1: Building a Native Ad with Brand Card (Manifest Mode) +### Example 1: Building a Native Ad with Brand Manifest (Manifest Mode) -#### Initial Request with Brand Card +#### Initial Request with Brand Manifest ```json { "message": "Create a native ad for Yahoo promoting Purina Pro Plan. Focus on the veterinarian recommendation and that real salmon is the #1 ingredient.", "format_id": "display_native", "output_mode": "manifest", - "brand_card": { + "brand_manifest": { "url": "https://purina.com", "name": "Purina Pro Plan", "logos": [ @@ -210,13 +210,13 @@ Creative agents need to understand format requirements to generate appropriate c } ``` -#### Simple Request (Minimal Brand Card) +#### Simple Request (Minimal Brand Manifest) ```json { "message": "Create a native ad for Yahoo promoting Purina Pro Plan. Focus on the veterinarian recommendation and that real salmon is the #1 ingredient. Use an informative and trustworthy tone with 'Learn More' as the CTA.", "format_id": "display_native", "output_mode": "manifest", - "brand_card": { + "brand_manifest": { "url": "https://purina.com" }, "assets": [ diff --git a/docs/creative/task-reference/preview_creative.md b/docs/creative/task-reference/preview_creative.md index a9e7d330f0..b1375f0d45 100644 --- a/docs/creative/task-reference/preview_creative.md +++ b/docs/creative/task-reference/preview_creative.md @@ -63,9 +63,9 @@ To test multiple scenarios, provide an `inputs` array - you'll get one preview p | `creative_manifest` | object | Yes | Complete creative manifest with all required assets | | `inputs` | array | No | Array of input sets for generating multiple preview variants | | `template_id` | string | No | Specific template for custom format rendering | -| `brand_card` | object | No | Brand information manifest providing context for dynamic previews | +| `brand_manifest` | object | No | Brand information manifest providing context for dynamic previews | | `promoted_products` | object | No | Products/offerings being promoted - provides product context for previews | -| `asset_filters` | object | No | Filters to select specific assets from brand card (tags, asset_types, exclude_tags) | +| `asset_filters` | object | No | Filters to select specific assets from brand manifest (tags, asset_types, exclude_tags) | ### Creative Manifest Structure @@ -524,7 +524,7 @@ Response: Each `preview_url` returns an HTML page with an embedded video player showing the geo-specific variant. -### Example 4: Using Brand Card for Dynamic Previews +### Example 4: Using Brand Manifest for Dynamic Previews Preview creative variants using brand context and product information: @@ -548,7 +548,7 @@ Preview creative variants using brand context and product information: } } }, - "brand_card": { + "brand_manifest": { "url": "https://acmecorp.com", "assets": [ { diff --git a/docs/media-buy/creatives/index.md b/docs/media-buy/creatives/index.md index 3954a9c8cb..184e3c5642 100644 --- a/docs/media-buy/creatives/index.md +++ b/docs/media-buy/creatives/index.md @@ -126,7 +126,7 @@ AdCP uses a centralized creative library where assets are uploaded once and assi - Reuse creatives across multiple media buys - Track performance across all assignments -Asset management is handled through [Brand Cards](../../reference/brand-card), which provide brand-level assets with tags for discovery. +Asset management is handled through [Brand Manifests](../../reference/brand-manifest), which provide brand-level assets with tags for discovery. ## Platform Considerations @@ -170,7 +170,7 @@ Creative operations have varying response times: - **[`sync_creatives`](../task-reference/sync_creatives)** - Bulk creative management with upsert semantics - **[`list_creatives`](../task-reference/list_creatives)** - Advanced creative library querying and filtering - **[`list_creative_formats`](../task-reference/list_creative_formats)** - Understanding format requirements -- **[Brand Card](../../reference/brand-card)** - Brand identity and asset management +- **[Brand Manifest](../../reference/brand-manifest)** - Brand identity and asset management - **[Creative Formats](../../creative/formats)** - Understanding format specifications and discovery - **[Creative Channel Guides](../../creative/channels/video)** - Format examples across video, display, audio, DOOH, and carousels - **[Asset Types](../../creative/asset-types)** - Understanding asset roles and specifications \ No newline at end of file diff --git a/docs/media-buy/task-reference/create_media_buy.md b/docs/media-buy/task-reference/create_media_buy.md index 011953640a..03b5fcf466 100644 --- a/docs/media-buy/task-reference/create_media_buy.md +++ b/docs/media-buy/task-reference/create_media_buy.md @@ -21,9 +21,9 @@ Create a media buy from selected packages. This task handles the complete workfl |-----------|------|----------|-------------| | `buyer_ref` | string | Yes | Buyer's reference identifier for this media buy | | `packages` | Package[] | Yes | Array of package configurations (see Package Object below) | -| `brand_card` | BrandCard | Yes | Brand information manifest serving as the namespace and identity for this media buy. Provides brand context, assets, and product catalog. Can be cached and reused across multiple requests. See [Brand Card](../../reference/brand-card) for details. | -| `promoted_products` | PromotedProducts | No | Products or offerings being promoted in this media buy. Supports SKU selection from brand card's product catalog, or inline offerings for non-commerce campaigns. | -| `promoted_offering` | string | No | **DEPRECATED**: Use `brand_card` with `promoted_products` instead. Legacy field for describing what is being promoted. | +| `brand_manifest` | BrandCard | Yes | Brand information manifest serving as the namespace and identity for this media buy. Provides brand context, assets, and product catalog. Can be cached and reused across multiple requests. See [Brand Manifest](../../reference/brand-manifest) for details. | +| `promoted_products` | PromotedProducts | No | Products or offerings being promoted in this media buy. Supports SKU selection from brand manifest's product catalog, or inline offerings for non-commerce campaigns. | +| `promoted_offering` | string | No | **DEPRECATED**: Use `brand_manifest` with `promoted_products` instead. Legacy field for describing what is being promoted. | | `po_number` | string | No | Purchase order number for tracking | | `start_time` | string | Yes | Campaign start time: `"asap"` to start as soon as possible, or ISO 8601 date-time for scheduled start | | `end_time` | string | Yes | Campaign end date/time in ISO 8601 format (UTC unless timezone specified) | diff --git a/docs/reference/brand-card.md b/docs/reference/brand-manifest.md similarity index 86% rename from docs/reference/brand-card.md rename to docs/reference/brand-manifest.md index 259406eb4e..45bf06e193 100644 --- a/docs/reference/brand-card.md +++ b/docs/reference/brand-manifest.md @@ -1,16 +1,16 @@ --- -title: Brand Card +title: Brand Manifest description: Standardized brand information manifest for creative generation and media buying -keywords: [brand card, brand manifest, creative generation, brand guidelines] +keywords: [brand manifest, brand manifest, creative generation, brand guidelines] --- -# Brand Card +# Brand Manifest -The **Brand Card** is a standardized manifest format that serves as the **namespace and identity** for brands in AdCP. It provides brand context, assets, and product catalogs that can be cached and reused across all media buys and creative generation requests. +The **Brand Manifest** is a standardized manifest format that serves as the **namespace and identity** for brands in AdCP. It provides brand context, assets, and product catalogs that can be cached and reused across all media buys and creative generation requests. ## Overview -Brand cards solve a key problem: how to efficiently provide brand context without requiring complex authorization flows or repeated data entry. By making the brand card the central identifier for all advertising activity, AdCP enables: +Brand cards solve a key problem: how to efficiently provide brand context without requiring complex authorization flows or repeated data entry. By making the brand manifest the central identifier for all advertising activity, AdCP enables: - **Natural grouping**: All media buys and creatives for a brand reference the same card - **Consistent identity**: Brand guidelines automatically travel with every request @@ -22,7 +22,7 @@ Brand cards solve a key problem: how to efficiently provide brand context withou - **Namespace**: Brand card serves as the primary identifier for all advertising activity - **Minimal Friction**: Start with just a name or URL, expand as needed -- **Cacheable**: Same brand card reused across all requests +- **Cacheable**: Same brand manifest reused across all requests - **Standardized**: Consistent format across all AdCP implementations - **Flexible**: Supports SMB to enterprise use cases - **AI-Optimized**: Structured for easy ingestion by creative agents @@ -31,7 +31,7 @@ Brand cards solve a key problem: how to efficiently provide brand context withou ### SMB / Ad Hoc Creative Generation -For small businesses or one-off campaigns, a minimal brand card provides enough context: +For small businesses or one-off campaigns, a minimal brand manifest provides enough context: ```json { @@ -43,7 +43,7 @@ Creative agents can infer brand information from the URL, pulling logos, colors, ### Enterprise / Established Brand -For established brands with defined guidelines, the brand card provides comprehensive context: +For established brands with defined guidelines, the brand manifest provides comprehensive context: ```json { @@ -125,9 +125,9 @@ Some brands don't have dedicated URLs (white-label products, local businesses, B - B2B brands without public sites - Sub-brands under parent company URLs -## Brand Card Schema +## Brand Manifest Schema -**Schema URL**: [/schemas/v1/core/brand-card.json](/schemas/v1/core/brand-card.json) +**Schema URL**: [/schemas/v1/core/brand-manifest.json](/schemas/v1/core/brand-manifest.json) ### Required Fields @@ -215,13 +215,13 @@ Some brands don't have dedicated URLs (white-label products, local businesses, B ### create_media_buy -Include brand card in media buy creation to provide context for creative generation: +Include brand manifest in media buy creation to provide context for creative generation: ```json { "buyer_ref": "campaign_2024_q1", "promoted_offering": "ACME Pro Widget", - "brand_card": { + "brand_manifest": { "url": "https://acmecorp.com", "name": "ACME Corporation", "tone": "professional and innovative" @@ -233,13 +233,13 @@ Include brand card in media buy creation to provide context for creative generat ### build_creative -Use brand card to inform creative generation: +Use brand manifest to inform creative generation: ```json { "message": "Create a native ad highlighting our new product launch", "format_id": "display_native", - "brand_card": { + "brand_manifest": { "url": "https://acmecorp.com", "logos": [ { @@ -291,12 +291,12 @@ Tags help creative agents select appropriate logo variants: } ``` -### 3. Cache and Reuse Brand Cards +### 3. Cache and Reuse Brand Manifests Brand cards are designed to be cached: ```javascript -// Cache brand card once +// Cache brand manifest once const brandCard = { url: "https://acmecorp.com", colors: {...}, @@ -304,9 +304,9 @@ const brandCard = { }; // Reuse across requests -await createMediaBuy({ brand_card: brandCard, ... }); -await buildCreative({ brand_card: brandCard, ... }); -await buildCreative({ brand_card: brandCard, ... }); // Same card, different creative +await createMediaBuy({ brand_manifest: brandCard, ... }); +await buildCreative({ brand_manifest: brandCard, ... }); +await buildCreative({ brand_manifest: brandCard, ... }); // Same card, different creative ``` ### 4. Product Feeds for Multi-SKU @@ -369,10 +369,10 @@ For implementations using the legacy `brand_guidelines` field in `build_creative } ``` -**After (Brand Card)**: +**After (Brand Manifest)**: ```json { - "brand_card": { + "brand_manifest": { "url": "https://brand.com", "colors": { "primary": "#FF6B35", diff --git a/static/schemas/v1/core/asset-type.json b/static/schemas/v1/core/asset-type.json index a27cf1c89d..1108967676 100644 --- a/static/schemas/v1/core/asset-type.json +++ b/static/schemas/v1/core/asset-type.json @@ -11,7 +11,7 @@ }, "type": { "type": "string", - "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "brand_card", "url"], + "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "brand_manifest", "url"], "description": "Type of asset" }, "required": { diff --git a/static/schemas/v1/core/brand-card.json b/static/schemas/v1/core/brand-manifest.json similarity index 98% rename from static/schemas/v1/core/brand-card.json rename to static/schemas/v1/core/brand-manifest.json index e16eac69e5..65a8ce3b87 100644 --- a/static/schemas/v1/core/brand-card.json +++ b/static/schemas/v1/core/brand-manifest.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/v1/core/brand-card.json", - "title": "Brand Card", + "$id": "/schemas/v1/core/brand-manifest.json", + "title": "Brand Manifest", "description": "Standardized brand information manifest for creative generation and media buying. Enables low-friction creative workflows by providing brand context that can be easily cached and shared across requests.", "type": "object", "properties": { @@ -259,12 +259,12 @@ "created_date": { "type": "string", "format": "date-time", - "description": "When this brand card was created" + "description": "When this brand manifest was created" }, "updated_date": { "type": "string", "format": "date-time", - "description": "When this brand card was last updated" + "description": "When this brand manifest was last updated" }, "version": { "type": "string", diff --git a/static/schemas/v1/core/creative-asset.json b/static/schemas/v1/core/creative-asset.json index a4c932f985..a8c42d0024 100644 --- a/static/schemas/v1/core/creative-asset.json +++ b/static/schemas/v1/core/creative-asset.json @@ -27,13 +27,13 @@ "properties": { "asset_type": { "type": "string", - "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "brand_card", "url"], + "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "brand_manifest", "url"], "description": "Type of this asset" }, "url": { "type": "string", "format": "uri", - "description": "URL for image/video/audio assets or brand_card URL" + "description": "URL for image/video/audio assets or brand_manifest URL" }, "content": { "type": "string", @@ -60,7 +60,7 @@ }, "colors": { "type": "object", - "description": "Brand colors (for brand_card asset type)", + "description": "Brand colors (for brand_manifest asset type)", "properties": { "primary": {"type": "string"}, "secondary": {"type": "string"}, @@ -69,12 +69,12 @@ }, "fonts": { "type": "array", - "description": "Brand fonts (for brand_card asset type)", + "description": "Brand fonts (for brand_manifest asset type)", "items": {"type": "string"} }, "tone": { "type": "string", - "description": "Brand tone/voice (for brand_card asset type)" + "description": "Brand tone/voice (for brand_manifest asset type)" } }, "required": ["asset_type"], diff --git a/static/schemas/v1/core/format.json b/static/schemas/v1/core/format.json index c7ca6ca4ee..14827f24b6 100644 --- a/static/schemas/v1/core/format.json +++ b/static/schemas/v1/core/format.json @@ -67,7 +67,7 @@ "asset_type": { "type": "string", "description": "Type of asset", - "enum": ["image", "video", "audio", "text", "html", "javascript", "url", "brand_card"] + "enum": ["image", "video", "audio", "text", "html", "javascript", "url", "brand_manifest"] }, "asset_role": { "type": "string", @@ -121,7 +121,7 @@ "asset_type": { "type": "string", "description": "Type of asset", - "enum": ["image", "video", "audio", "text", "html", "javascript", "url", "brand_card"] + "enum": ["image", "video", "audio", "text", "html", "javascript", "url", "brand_manifest"] }, "asset_role": { "type": "string", diff --git a/static/schemas/v1/core/promoted-products.json b/static/schemas/v1/core/promoted-products.json index 74ac1d3ec9..7a347f5bf8 100644 --- a/static/schemas/v1/core/promoted-products.json +++ b/static/schemas/v1/core/promoted-products.json @@ -56,7 +56,7 @@ }, "assets": { "type": "array", - "description": "Campaign-specific assets for this offering (in addition to brand card assets)", + "description": "Campaign-specific assets for this offering (in addition to brand manifest assets)", "items": { "type": "object", "properties": { diff --git a/static/schemas/v1/creative/preview-creative-request.json b/static/schemas/v1/creative/preview-creative-request.json index f1ddf5d970..f213534025 100644 --- a/static/schemas/v1/creative/preview-creative-request.json +++ b/static/schemas/v1/creative/preview-creative-request.json @@ -49,8 +49,8 @@ "type": "string", "description": "Specific template ID for custom format rendering" }, - "brand_card": { - "$ref": "/schemas/v1/core/brand-card.json", + "brand_manifest": { + "$ref": "/schemas/v1/core/brand-manifest.json", "description": "Brand information manifest providing context for preview generation. Used when creative manifest includes dynamic elements that need brand context." }, "promoted_products": { @@ -59,7 +59,7 @@ }, "asset_filters": { "type": "object", - "description": "Filters to select specific assets from the brand card's asset library for preview variants", + "description": "Filters to select specific assets from the brand manifest's asset library for preview variants", "properties": { "tags": { "type": "array", diff --git a/static/schemas/v1/index.json b/static/schemas/v1/index.json index 2e7da3cb7a..38d4b5231e 100644 --- a/static/schemas/v1/index.json +++ b/static/schemas/v1/index.json @@ -83,8 +83,8 @@ "$ref": "/schemas/v1/core/property.json", "description": "An advertising property that can be validated via adagents.json" }, - "brand-card": { - "$ref": "/schemas/v1/core/brand-card.json", + "brand-manifest": { + "$ref": "/schemas/v1/core/brand-manifest.json", "description": "Standardized brand information manifest for creative generation and media buying" }, "promoted-products": { diff --git a/static/schemas/v1/media-buy/build-creative-request.json b/static/schemas/v1/media-buy/build-creative-request.json index 90cd8d3c20..a359f31e1b 100644 --- a/static/schemas/v1/media-buy/build-creative-request.json +++ b/static/schemas/v1/media-buy/build-creative-request.json @@ -41,17 +41,17 @@ "default": "manifest", "description": "Output format - 'manifest' for asset-based creatives, 'code' for executable creative code" }, - "brand_card": { - "$ref": "/schemas/v1/core/brand-card.json", + "brand_manifest": { + "$ref": "/schemas/v1/core/brand-manifest.json", "description": "Brand information manifest providing context for creative generation. Replaces brand_guidelines with a more comprehensive and standardized format." }, "promoted_products": { "$ref": "/schemas/v1/core/promoted-products.json", - "description": "Products or offerings being promoted in this creative. Supports SKU selection from brand card's product catalog, or inline offerings for non-commerce campaigns." + "description": "Products or offerings being promoted in this creative. Supports SKU selection from brand manifest's product catalog, or inline offerings for non-commerce campaigns." }, "asset_filters": { "type": "object", - "description": "Filters to select specific assets from the brand card's asset library", + "description": "Filters to select specific assets from the brand manifest's asset library", "properties": { "tags": { "type": "array", @@ -80,7 +80,7 @@ }, "brand_guidelines": { "type": "object", - "description": "DEPRECATED: Use brand_card instead. Legacy brand-specific guidelines and constraints.", + "description": "DEPRECATED: Use brand_manifest instead. Legacy brand-specific guidelines and constraints.", "properties": { "colors": { "type": "array", diff --git a/static/schemas/v1/media-buy/create-media-buy-request.json b/static/schemas/v1/media-buy/create-media-buy-request.json index e280869f28..3e4f0e7094 100644 --- a/static/schemas/v1/media-buy/create-media-buy-request.json +++ b/static/schemas/v1/media-buy/create-media-buy-request.json @@ -22,17 +22,17 @@ "$ref": "/schemas/v1/media-buy/package-request.json" } }, - "brand_card": { - "$ref": "/schemas/v1/core/brand-card.json", + "brand_manifest": { + "$ref": "/schemas/v1/core/brand-manifest.json", "description": "Brand information manifest serving as the namespace and identity for this media buy. Provides brand context, assets, and product catalog. Can be cached and reused across multiple requests." }, "promoted_offering": { "type": "string", - "description": "DEPRECATED: Use brand_card with promoted_products instead. Legacy field for describing what is being promoted." + "description": "DEPRECATED: Use brand_manifest with promoted_products instead. Legacy field for describing what is being promoted." }, "promoted_products": { "$ref": "/schemas/v1/core/promoted-products.json", - "description": "Products or offerings being promoted in this media buy. Supports SKU selection from brand card's product catalog, or inline offerings for non-commerce campaigns." + "description": "Products or offerings being promoted in this media buy. Supports SKU selection from brand manifest's product catalog, or inline offerings for non-commerce campaigns." }, "po_number": { "type": "string", @@ -78,6 +78,6 @@ ] } }, - "required": ["buyer_ref", "packages", "brand_card", "start_time", "end_time", "budget"], + "required": ["buyer_ref", "packages", "brand_manifest", "start_time", "end_time", "budget"], "additionalProperties": false } \ No newline at end of file diff --git a/static/schemas/v1/media-buy/sync-creatives-approval-request.json b/static/schemas/v1/media-buy/sync-creatives-approval-request.json index 0ba76d6b60..a800146d8a 100644 --- a/static/schemas/v1/media-buy/sync-creatives-approval-request.json +++ b/static/schemas/v1/media-buy/sync-creatives-approval-request.json @@ -41,7 +41,7 @@ "properties": { "asset_type": { "type": "string", - "enum": ["text", "brand_card"] + "enum": ["text", "brand_manifest"] }, "content": { "type": "string", @@ -50,7 +50,7 @@ "url": { "type": "string", "format": "uri", - "description": "Updated brand card URL" + "description": "Updated brand manifest URL" } }, "required": ["asset_type"] From 32efe4f365a899513753b34a14fe2258a964cb90 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 09:37:28 -0400 Subject: [PATCH 03/11] fix: address PR review comments on generative creatives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addressed all 12 review comments: Schema improvements: - Rename promoted-products fields to manifest_* prefix for clarity - Simplify promoted-products to use standard asset structure - Create promoted-offerings schema combining brand_manifest and selectors - Remove deprecated brand_guidelines from build-creative-request Documentation improvements: - Expand brand_manifest description to explain its comprehensive role - Remove brand_manifest parameter from preview_creative (should be in manifest) - Change "filters" to "selectors" for consistency - Clarify that brand_manifest belongs in creative manifest for dynamic creatives These changes improve schema consistency, reduce duplication, and make the relationship between brand manifests and product selection clearer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../creative/task-reference/build_creative.md | 2 +- .../task-reference/preview_creative.md | 6 +- .../schemas/v1/core/promoted-offerings.json | 62 ++++++++ static/schemas/v1/core/promoted-products.json | 143 ++++-------------- .../v1/media-buy/build-creative-request.json | 26 ---- 5 files changed, 91 insertions(+), 148 deletions(-) create mode 100644 static/schemas/v1/core/promoted-offerings.json diff --git a/docs/creative/task-reference/build_creative.md b/docs/creative/task-reference/build_creative.md index 1f380d17c0..eac5b713ed 100644 --- a/docs/creative/task-reference/build_creative.md +++ b/docs/creative/task-reference/build_creative.md @@ -41,7 +41,7 @@ Creative agents need to understand format requirements to generate appropriate c | `format_source` | string | No | Source URL for format lookup (sales agent URL). If null/omitted, assumes standard AdCP format | | `format_id` | string | Yes | Format identifier to look up from the format source | | `context_id` | string | No | Session context from previous message for continuity | -| `brand_manifest` | BrandCard | No | Brand information manifest for creative generation context. See [Brand Manifest](../../reference/brand-manifest) for details. Replaces legacy `brand_guidelines`. | +| `brand_manifest` | BrandCard | No | Brand information manifest containing all assets, themes, and information necessary to ensure creatives are aligned with the brand's goals and that the publisher is comfortable with what's being advertised. See [Brand Manifest](../../reference/brand-manifest) for details. | | `assets` | array | No | References to asset libraries and specific assets | | `output_mode` | string | No | `"manifest"` for creative manifest or `"code"` for executable (default: `"manifest"`) | | `preview_options` | object | No | Options for generating preview | diff --git a/docs/creative/task-reference/preview_creative.md b/docs/creative/task-reference/preview_creative.md index b1375f0d45..6e7d7e6162 100644 --- a/docs/creative/task-reference/preview_creative.md +++ b/docs/creative/task-reference/preview_creative.md @@ -60,12 +60,10 @@ To test multiple scenarios, provide an `inputs` array - you'll get one preview p | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `format_id` | string | Yes | Format identifier for rendering | -| `creative_manifest` | object | Yes | Complete creative manifest with all required assets | +| `creative_manifest` | object | Yes | Complete creative manifest with all required assets (brand_manifest should be included in the manifest for dynamic creatives) | | `inputs` | array | No | Array of input sets for generating multiple preview variants | | `template_id` | string | No | Specific template for custom format rendering | -| `brand_manifest` | object | No | Brand information manifest providing context for dynamic previews | -| `promoted_products` | object | No | Products/offerings being promoted - provides product context for previews | -| `asset_filters` | object | No | Filters to select specific assets from brand manifest (tags, asset_types, exclude_tags) | +| `asset_selectors` | object | No | Selectors to choose specific assets from the creative manifest (tags, asset_types, exclude_tags) | ### Creative Manifest Structure diff --git a/static/schemas/v1/core/promoted-offerings.json b/static/schemas/v1/core/promoted-offerings.json new file mode 100644 index 0000000000..3b654b014a --- /dev/null +++ b/static/schemas/v1/core/promoted-offerings.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/promoted-offerings.json", + "title": "Promoted Offerings", + "description": "Complete offering specification combining brand manifest, product selectors, and asset filters. Provides all context needed for creative generation about what is being promoted.", + "type": "object", + "properties": { + "brand_manifest": { + "$ref": "/schemas/v1/core/brand-manifest.json", + "description": "Brand information manifest containing assets, themes, and guidelines" + }, + "product_selectors": { + "$ref": "/schemas/v1/core/promoted-products.json", + "description": "Selectors to choose which products/offerings from the brand manifest to promote" + }, + "asset_selectors": { + "type": "object", + "description": "Selectors to choose specific assets from the brand manifest", + "properties": { + "tags": { + "type": "array", + "description": "Select assets with specific tags (e.g., ['holiday', 'premium'])", + "items": { + "type": "string" + } + }, + "asset_types": { + "type": "array", + "description": "Filter by asset type (e.g., ['image', 'video'])", + "items": { + "type": "string", + "enum": ["image", "video", "audio", "text", "html", "css", "javascript"] + } + }, + "exclude_tags": { + "type": "array", + "description": "Exclude assets with these tags", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "required": ["brand_manifest"], + "additionalProperties": false, + "examples": [ + { + "brand_manifest": { + "url": "https://brand.com" + }, + "product_selectors": { + "manifest_skus": ["SKU-123", "SKU-456"] + }, + "asset_selectors": { + "tags": ["holiday"], + "asset_types": ["image", "video"] + } + } + ] +} diff --git a/static/schemas/v1/core/promoted-products.json b/static/schemas/v1/core/promoted-products.json index 7a347f5bf8..24df8bffaa 100644 --- a/static/schemas/v1/core/promoted-products.json +++ b/static/schemas/v1/core/promoted-products.json @@ -2,157 +2,66 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/v1/core/promoted-products.json", "title": "Promoted Products", - "description": "Specification of products or offerings being promoted in a campaign. Supports multiple selection methods that can be combined using UNION (OR) logic: direct SKUs, tag-based filtering, category selection, natural language queries, and inline offering definitions for non-commerce campaigns. When multiple selection methods are provided, products matching ANY of the criteria are selected (logical OR, not AND).", + "description": "Specification of products or offerings being promoted in a campaign. Supports multiple selection methods from the brand manifest that can be combined using UNION (OR) logic. When multiple selection methods are provided, products matching ANY of the criteria are selected (logical OR, not AND).", "type": "object", "properties": { - "skus": { + "manifest_skus": { "type": "array", - "description": "Direct product SKU references from the brand's product catalog", + "description": "Direct product SKU references from the brand manifest product catalog", "items": { "type": "string" } }, - "tags": { + "manifest_tags": { "type": "array", - "description": "Select products by tags from the product catalog (e.g., 'organic', 'sauces', 'holiday')", + "description": "Select products by tags from the brand manifest product catalog (e.g., 'organic', 'sauces', 'holiday')", "items": { "type": "string" } }, - "category": { + "manifest_category": { "type": "string", - "description": "Select products from a specific category in the product catalog (e.g., 'beverages/soft-drinks', 'food/sauces')" + "description": "Select products from a specific category in the brand manifest product catalog (e.g., 'beverages/soft-drinks', 'food/sauces')" }, - "query": { + "manifest_query": { "type": "string", - "description": "Natural language query to select products (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" + "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" }, - "offerings": { + "additional_assets": { "type": "array", - "description": "Inline offering definitions for non-commerce campaigns or campaigns without a product catalog. Use when promoting services, events, brand awareness, or other non-SKU offerings.", + "description": "Campaign-specific assets to supplement brand manifest assets for promoted products", "items": { "type": "object", - "properties": { - "offering_id": { - "type": "string", - "description": "Unique identifier for this offering" - }, - "name": { - "type": "string", - "description": "Name of the offering being promoted" - }, - "description": { - "type": "string", - "description": "Description of what is being promoted" - }, - "message": { - "type": "string", - "description": "Product-specific message or context for creative generation. Provides additional context beyond the overall campaign message." - }, - "link": { - "type": "string", - "format": "uri", - "description": "Landing page or destination URL for this offering" - }, - "assets": { - "type": "array", - "description": "Campaign-specific assets for this offering (in addition to brand manifest assets)", - "items": { - "type": "object", - "properties": { - "asset_id": { - "type": "string" - }, - "asset_type": { - "type": "string", - "enum": ["image", "video", "audio", "text"] - }, - "url": { - "type": "string", - "format": "uri" - }, - "tags": { - "type": "array", - "items": { - "type": "string" - } - }, - "width": { - "type": "integer" - }, - "height": { - "type": "integer" - }, - "duration_seconds": { - "type": "number" - }, - "format": { - "type": "string" - } - }, - "required": ["asset_id", "asset_type", "url"] - } - }, - "metadata": { - "type": "object", - "description": "Additional offering-specific metadata", - "additionalProperties": true - } - }, - "required": ["name", "description"], - "additionalProperties": false + "description": "Additional asset definition - uses standard asset structure", + "additionalProperties": true } } }, "additionalProperties": false, "examples": [ { - "skus": ["SKU-12345", "SKU-67890"], - "description": "Direct SKU selection for specific products" + "manifest_skus": ["SKU-12345", "SKU-67890"], + "description": "Direct SKU selection for specific products from brand manifest" }, { - "tags": ["organic", "sauces"], - "category": "food/condiments", - "description": "UNION selection: products tagged 'organic' OR 'sauces' OR in 'food/condiments' category" + "manifest_tags": ["organic", "sauces"], + "manifest_category": "food/condiments", + "description": "UNION selection: products tagged 'organic' OR 'sauces' OR in 'food/condiments' category from brand manifest" }, { - "query": "all Kraft Heinz pasta sauces under $5", - "description": "Natural language product selection" + "manifest_query": "all Kraft Heinz pasta sauces under $5", + "description": "Natural language product selection from brand manifest" }, { - "offerings": [ + "manifest_tags": ["holiday"], + "additional_assets": [ { - "offering_id": "q1_brand_awareness", - "name": "Q1 Brand Awareness Campaign", - "description": "Promote our new sustainability initiative and brand values", - "message": "Focus on environmental impact and corporate responsibility", - "link": "https://brand.com/sustainability", - "assets": [ - { - "asset_id": "sustainability_hero", - "asset_type": "image", - "url": "https://cdn.brand.com/sustainability-hero.jpg", - "tags": ["campaign", "sustainability"], - "width": 1920, - "height": 1080, - "format": "jpg" - } - ] + "asset_id": "holiday_hero", + "asset_type": "image", + "url": "https://cdn.brand.com/holiday-hero.jpg" } ], - "description": "Non-commerce campaign with inline offering" - }, - { - "skus": ["SKU-123"], - "offerings": [ - { - "offering_id": "holiday_promo", - "name": "Holiday Bundle Promotion", - "description": "Buy 2 get 1 free holiday special", - "message": "Limited time offer - perfect for gift giving" - } - ], - "description": "Combine catalog SKUs with promotional offering context" + "description": "Select products from manifest and add campaign-specific assets" } ] } diff --git a/static/schemas/v1/media-buy/build-creative-request.json b/static/schemas/v1/media-buy/build-creative-request.json index a359f31e1b..f12efbbcbd 100644 --- a/static/schemas/v1/media-buy/build-creative-request.json +++ b/static/schemas/v1/media-buy/build-creative-request.json @@ -77,32 +77,6 @@ } }, "additionalProperties": false - }, - "brand_guidelines": { - "type": "object", - "description": "DEPRECATED: Use brand_manifest instead. Legacy brand-specific guidelines and constraints.", - "properties": { - "colors": { - "type": "array", - "items": {"type": "string"}, - "description": "Brand color palette (hex codes)" - }, - "fonts": { - "type": "array", - "items": {"type": "string"}, - "description": "Approved brand fonts" - }, - "tone": { - "type": "string", - "description": "Brand tone and voice guidelines" - }, - "restrictions": { - "type": "array", - "items": {"type": "string"}, - "description": "Content restrictions or requirements" - } - }, - "additionalProperties": false } }, "required": ["message", "format_id"], From 12cb8fd2fd315f62e21db657a16af988cb2f82d0 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 14:21:29 -0400 Subject: [PATCH 04/11] docs: address second round of PR review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarifications and consistency improvements: 1. build_creative format_id: Clarified it's the TARGET format you want to generate 2. preview_creative asset_selectors: Removed - selectors should be applied during manifest creation, not at preview time 3. preview_creative Example 4: Replaced with simpler template variable example that doesn't use deprecated separate brand_manifest parameter 4. create_media_buy promoted_products: Added clarification on why it's needed at media buy level (reporting, compliance, publisher understanding) These changes make the relationship between manifests, selectors, and preview clearer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../creative/task-reference/build_creative.md | 2 +- .../task-reference/preview_creative.md | 72 +++++++------------ .../task-reference/create_media_buy.md | 2 +- 3 files changed, 28 insertions(+), 48 deletions(-) diff --git a/docs/creative/task-reference/build_creative.md b/docs/creative/task-reference/build_creative.md index eac5b713ed..074ec05cc1 100644 --- a/docs/creative/task-reference/build_creative.md +++ b/docs/creative/task-reference/build_creative.md @@ -39,7 +39,7 @@ Creative agents need to understand format requirements to generate appropriate c |-----------|------|----------|-------------| | `message` | string | Yes | The request message (initial brief or refinement instructions) | | `format_source` | string | No | Source URL for format lookup (sales agent URL). If null/omitted, assumes standard AdCP format | -| `format_id` | string | Yes | Format identifier to look up from the format source | +| `format_id` | string | Yes | Target format identifier - the format you want to generate (e.g., 'display_native', 'video_30s') | | `context_id` | string | No | Session context from previous message for continuity | | `brand_manifest` | BrandCard | No | Brand information manifest containing all assets, themes, and information necessary to ensure creatives are aligned with the brand's goals and that the publisher is comfortable with what's being advertised. See [Brand Manifest](../../reference/brand-manifest) for details. | | `assets` | array | No | References to asset libraries and specific assets | diff --git a/docs/creative/task-reference/preview_creative.md b/docs/creative/task-reference/preview_creative.md index 6e7d7e6162..d38346f1fa 100644 --- a/docs/creative/task-reference/preview_creative.md +++ b/docs/creative/task-reference/preview_creative.md @@ -63,7 +63,6 @@ To test multiple scenarios, provide an `inputs` array - you'll get one preview p | `creative_manifest` | object | Yes | Complete creative manifest with all required assets (brand_manifest should be included in the manifest for dynamic creatives) | | `inputs` | array | No | Array of input sets for generating multiple preview variants | | `template_id` | string | No | Specific template for custom format rendering | -| `asset_selectors` | object | No | Selectors to choose specific assets from the creative manifest (tags, asset_types, exclude_tags) | ### Creative Manifest Structure @@ -522,9 +521,9 @@ Response: Each `preview_url` returns an HTML page with an embedded video player showing the geo-specific variant. -### Example 4: Using Brand Manifest for Dynamic Previews +### Example 4: Dynamic Creative with Template Variables -Preview creative variants using brand context and product information: +Preview a dynamic creative that uses template variables for personalization: ```json { @@ -534,11 +533,15 @@ Preview creative variants using brand context and product information: "assets": { "headline": { "asset_type": "text", - "content": "{PRODUCT_NAME} - {TAGLINE}" + "content": "Discover {PRODUCT_NAME}" + }, + "description": { + "asset_type": "text", + "content": "{PRODUCT_DESCRIPTION}" }, "main_image": { "asset_type": "image", - "url": "{BRAND_ASSET_URL}" + "url": "https://cdn.acmecorp.com/hero.jpg" }, "cta": { "asset_type": "text", @@ -546,37 +549,20 @@ Preview creative variants using brand context and product information: } } }, - "brand_manifest": { - "url": "https://acmecorp.com", - "assets": [ - { - "asset_id": "hero_holiday", - "asset_type": "image", - "url": "https://cdn.acmecorp.com/holiday-hero.jpg", - "tags": ["holiday", "winter", "hero"] - } - ] - }, - "promoted_products": { - "skus": ["WIDGET-PRO-2024"] - }, - "asset_filters": { - "tags": ["holiday", "winter"] - }, "inputs": [ { - "name": "Holiday Desktop", + "name": "Product A", "macros": { - "DEVICE_TYPE": "desktop" - }, - "context_description": "Holiday shopping season, premium product showcase" + "PRODUCT_NAME": "Widget Pro 2024", + "PRODUCT_DESCRIPTION": "Premium quality you can trust" + } }, { - "name": "Holiday Mobile", + "name": "Product B", "macros": { - "DEVICE_TYPE": "mobile" - }, - "context_description": "Mobile browsing during holiday sales" + "PRODUCT_NAME": "Widget Lite", + "PRODUCT_DESCRIPTION": "Affordable excellence for everyone" + } } ] } @@ -589,33 +575,27 @@ Response: "adcp_version": "1.0.0", "previews": [ { - "preview_url": "https://creative-agent.example.com/preview/brand123/desktop", + "preview_url": "https://creative-agent.example.com/preview/dynamic123/a", "input": { - "name": "Holiday Desktop", + "name": "Product A", "macros": { - "DEVICE_TYPE": "desktop", "PRODUCT_NAME": "Widget Pro 2024", - "TAGLINE": "Premium Quality You Can Trust", - "BRAND_ASSET_URL": "https://cdn.acmecorp.com/holiday-hero.jpg" - }, - "context_description": "Holiday shopping season, premium product showcase" + "PRODUCT_DESCRIPTION": "Premium quality you can trust" + } } }, { - "preview_url": "https://creative-agent.example.com/preview/brand123/mobile", + "preview_url": "https://creative-agent.example.com/preview/dynamic123/b", "input": { - "name": "Holiday Mobile", + "name": "Product B", "macros": { - "DEVICE_TYPE": "mobile", - "PRODUCT_NAME": "Widget Pro 2024", - "TAGLINE": "Premium Quality You Can Trust", - "BRAND_ASSET_URL": "https://cdn.acmecorp.com/holiday-hero.jpg" - }, - "context_description": "Mobile browsing during holiday sales" + "PRODUCT_NAME": "Widget Lite", + "PRODUCT_DESCRIPTION": "Affordable excellence for everyone" + } } } ], - "interactive_url": "https://creative-agent.example.com/preview/brand123/interactive", + "interactive_url": "https://creative-agent.example.com/preview/dynamic123/interactive", "expires_at": "2025-02-15T18:00:00Z" } ``` diff --git a/docs/media-buy/task-reference/create_media_buy.md b/docs/media-buy/task-reference/create_media_buy.md index 03b5fcf466..8cab72240a 100644 --- a/docs/media-buy/task-reference/create_media_buy.md +++ b/docs/media-buy/task-reference/create_media_buy.md @@ -22,7 +22,7 @@ Create a media buy from selected packages. This task handles the complete workfl | `buyer_ref` | string | Yes | Buyer's reference identifier for this media buy | | `packages` | Package[] | Yes | Array of package configurations (see Package Object below) | | `brand_manifest` | BrandCard | Yes | Brand information manifest serving as the namespace and identity for this media buy. Provides brand context, assets, and product catalog. Can be cached and reused across multiple requests. See [Brand Manifest](../../reference/brand-manifest) for details. | -| `promoted_products` | PromotedProducts | No | Products or offerings being promoted in this media buy. Supports SKU selection from brand manifest's product catalog, or inline offerings for non-commerce campaigns. | +| `promoted_products` | PromotedProducts | No | Products or offerings being promoted in this media buy. Useful for campaign-level reporting, policy compliance, and publisher understanding of what's being advertised. Selects from brand manifest's product catalog using SKUs, tags, categories, or natural language queries. | | `promoted_offering` | string | No | **DEPRECATED**: Use `brand_manifest` with `promoted_products` instead. Legacy field for describing what is being promoted. | | `po_number` | string | No | Purchase order number for tracking | | `start_time` | string | Yes | Campaign start time: `"asap"` to start as soon as possible, or ISO 8601 date-time for scheduled start | From 32d2203e19a487c50a1d97ca8d4c830526f6c0cc Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 14:33:53 -0400 Subject: [PATCH 05/11] refactor: simplify schemas and clarify format identification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## sync_creatives Response Simplification Dramatically simplified the response structure: - Removed redundant summary object (counts can be derived) - Renamed results → creatives (clearer intent) - Merged assignment results into creative objects - Flattened preview structure (single preview_url instead of array) - Removed suggested_adaptations (belongs in separate optimization task) - Made fields conditional - only include when relevant Result: ~180 lines → ~110 lines with no loss of functionality. ## build_creative Format ID Improvements - Removed format_source parameter (namespace is embedded in format_id) - Added source_format_id and target_format_id parameters - Removed output_mode parameter (format definition determines output type) - Updated documentation to reflect namespaced format IDs Format IDs now support: - Standard: "display_native" - Namespaced: "https://publisher.com/.well-known/adcp/sales:custom_format" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../creative/task-reference/build_creative.md | 72 +++--- .../v1/media-buy/build-creative-request.json | 19 +- .../v1/media-buy/sync-creatives-response.json | 220 ++---------------- 3 files changed, 67 insertions(+), 244 deletions(-) diff --git a/docs/creative/task-reference/build_creative.md b/docs/creative/task-reference/build_creative.md index 074ec05cc1..5db8e10dbd 100644 --- a/docs/creative/task-reference/build_creative.md +++ b/docs/creative/task-reference/build_creative.md @@ -7,29 +7,33 @@ sidebar_position: 13 Build creative content for a specific format using a creative agent that can generate either a creative manifest (static mode) or executable code (dynamic mode). This tool supports conversational refinement through a series of messages. -## Format Lookup +## Format Identification -Creative agents need to understand format requirements to generate appropriate creatives. The format lookup process works as follows: +Format IDs support namespacing to distinguish between standard and custom formats: -1. **Standard AdCP Formats**: If `format_source` is omitted or null, the `format_id` refers to a standard AdCP format (e.g., "display_native", "video_standard_30s") +1. **Standard AdCP Formats**: Simple ID like `"display_native"` or `"video_standard_30s"` -2. **Publisher-Specific Formats**: If `format_source` is provided, the creative agent calls `list_creative_formats` on that sales agent URL to discover the format definition +2. **Publisher-Specific Formats**: Namespaced with URL like `"https://publisher.com/.well-known/adcp/sales:premium_video_30s"` -3. **Format Discovery**: Sales agents should make `list_creative_formats` accessible without authentication for creative agents to discover format requirements +3. **Format Discovery**: Use `list_creative_formats` to discover available formats and their requirements -### Format Source Examples +### Format ID Examples ```json // Standard AdCP format { - "format_id": "display_native" - // format_source omitted = standard format + "target_format_id": "display_native" } -// Publisher-specific format +// Publisher-specific format (namespaced) { - "format_id": "premium_video_30s", - "format_source": "https://publisher.com/.well-known/adcp/sales" + "target_format_id": "https://publisher.com/.well-known/adcp/sales:premium_video_30s" +} + +// Transform existing creative to new format +{ + "source_format_id": "display_300x250", + "target_format_id": "display_native" } ``` @@ -38,12 +42,11 @@ Creative agents need to understand format requirements to generate appropriate c | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `message` | string | Yes | The request message (initial brief or refinement instructions) | -| `format_source` | string | No | Source URL for format lookup (sales agent URL). If null/omitted, assumes standard AdCP format | -| `format_id` | string | Yes | Target format identifier - the format you want to generate (e.g., 'display_native', 'video_30s') | +| `source_format_id` | string | No | Format ID of existing creative to transform (optional - omit when creating from scratch). Supports namespaced IDs. | +| `target_format_id` | string | Yes | Format ID to generate (e.g., 'display_native', 'video_30s'). Supports namespaced IDs like 'https://publisher.com/.well-known/adcp/sales:custom_format'. The format definition specifies whether output is manifest-based or code-based. | | `context_id` | string | No | Session context from previous message for continuity | -| `brand_manifest` | BrandCard | No | Brand information manifest containing all assets, themes, and information necessary to ensure creatives are aligned with the brand's goals and that the publisher is comfortable with what's being advertised. See [Brand Manifest](../../reference/brand-manifest) for details. | +| `brand_manifest` | BrandManifest | No | Brand information manifest containing all assets, themes, and information necessary to ensure creatives are aligned with the brand's goals and that the publisher is comfortable with what's being advertised. See [Brand Manifest](../../reference/brand-manifest) for details. | | `assets` | array | No | References to asset libraries and specific assets | -| `output_mode` | string | No | `"manifest"` for creative manifest or `"code"` for executable (default: `"manifest"`) | | `preview_options` | object | No | Options for generating preview | | `finalize` | boolean | No | Set to true to finalize the creative (default: false) | @@ -105,11 +108,13 @@ Creative agents need to understand format requirements to generate appropriate c ### Creative Output Formats -#### Manifest Mode (Static Creative) +The format definition determines whether the output is a creative manifest (asset-based) or executable code (dynamic). Both types are shown below. + +#### Creative Manifest (Asset-Based) ```json { "type": "creative_manifest", - "format_id": "display_native", + "target_format_id": "display_native", "assets": { "headline": "Premium Dog Nutrition", "description": "Veterinarian recommended formula with real salmon", @@ -136,11 +141,11 @@ Creative agents need to understand format requirements to generate appropriate c } ``` -#### Code Mode (Dynamic Creative) +#### Creative Code (Dynamic) ```json { "type": "creative_code", - "format_id": "html5", + "target_format_id": "html5", "code": "
\n \n
", "dependencies": { "context_required": ["weather", "time"], @@ -175,14 +180,13 @@ Creative agents need to understand format requirements to generate appropriate c ## Examples -### Example 1: Building a Native Ad with Brand Manifest (Manifest Mode) +### Example 1: Building a Native Ad with Brand Manifest #### Initial Request with Brand Manifest ```json { "message": "Create a native ad for Yahoo promoting Purina Pro Plan. Focus on the veterinarian recommendation and that real salmon is the #1 ingredient.", - "format_id": "display_native", - "output_mode": "manifest", + "target_format_id": "display_native", "brand_manifest": { "url": "https://purina.com", "name": "Purina Pro Plan", @@ -214,8 +218,7 @@ Creative agents need to understand format requirements to generate appropriate c ```json { "message": "Create a native ad for Yahoo promoting Purina Pro Plan. Focus on the veterinarian recommendation and that real salmon is the #1 ingredient. Use an informative and trustworthy tone with 'Learn More' as the CTA.", - "format_id": "display_native", - "output_mode": "manifest", + "target_format_id": "display_native", "brand_manifest": { "url": "https://purina.com" }, @@ -236,7 +239,7 @@ Creative agents need to understand format requirements to generate appropriate c "status": "draft", "creative_output": { "type": "creative_manifest", - "format_id": "display_native", + "target_format_id": "display_native", "assets": { "headline": "Veterinarian Recommended Nutrition", "description": "Pro Plan with real salmon as the #1 ingredient provides complete nutrition for your dog's sensitive skin and stomach.", @@ -307,7 +310,7 @@ Creative agents need to understand format requirements to generate appropriate c "status": "ready", "creative_output": { "type": "creative_manifest", - "format_id": "display_native", + "target_format_id": "display_native", "assets": { "headline": "Veterinarian Recommended Grain-Free Nutrition", "description": "Pro Plan's grain-free formula with real salmon as the #1 ingredient provides complete nutrition without grains that can upset sensitive stomachs.", @@ -339,7 +342,7 @@ Creative agents need to understand format requirements to generate appropriate c "status": "ready", "creative_output": { "type": "creative_manifest", - "format_id": "display_native", + "target_format_id": "display_native", "assets": { "headline": "Veterinarian Recommended Grain-Free Nutrition", "description": "Pro Plan's grain-free formula with real salmon as the #1 ingredient provides complete nutrition without grains that can upset sensitive stomachs.", @@ -359,14 +362,13 @@ Creative agents need to understand format requirements to generate appropriate c } ``` -### Example 2: Building Dynamic Video Creative (Code Mode) +### Example 2: Building Dynamic Video Creative #### Initial Conversation ```json { "message": "I need a dynamic 30-second video for Purina that adapts based on viewer context. It should be upbeat and personalized, focusing on premium nutrition tailored for each dog's needs. The CTA should be 'Find Your Formula'.", - "format_id": "video_standard_30s", - "output_mode": "code", + "target_format_id": "video_standard_30s", "assets": [ { "library_id": "purina_video_library", @@ -431,9 +433,8 @@ Creative agents need to understand format requirements to generate appropriate c ```json { "message": "Create a short-form video ad featuring user-generated content style. Keep it authentic and fun, focusing on real pet parents and their transformation stories. Use 'See Their Story' as the CTA.", - "format_id": "custom_short_form_video", + "target_format_id": "custom_short_form_video", "format_source": "https://videoplatform.com/.well-known/adcp/sales", - "output_mode": "manifest" } ``` @@ -442,7 +443,7 @@ Creative agents need to understand format requirements to generate appropriate c { "context_id": "ctx-video-123", "creative": { - "format_id": "custom_short_form_video", + "target_format_id": "custom_short_form_video", "format_source": "https://videoplatform.com/.well-known/adcp/sales", "id": "custom_short_form_video", "name": "Short Form Video Ad", @@ -502,8 +503,9 @@ Creative agents need to understand format requirements to generate appropriate c ## Usage Notes -- **Manifest Mode**: Returns structured data that can be used with any ad server -- **Code Mode**: Returns executable HTML/JS that handles its own rendering +- **Creative Manifest**: Returns structured asset data that can be used with any ad server +- **Creative Code**: Returns executable HTML/JS that handles its own rendering +- **Output Type**: Determined by the format definition, not a parameter - **Previews**: Always check previews to ensure creative meets expectations - **Custom Formats**: Publishers should provide preview templates for non-standard formats - **Conversations**: Use natural language messages to guide the creative process diff --git a/static/schemas/v1/media-buy/build-creative-request.json b/static/schemas/v1/media-buy/build-creative-request.json index f12efbbcbd..a585bee863 100644 --- a/static/schemas/v1/media-buy/build-creative-request.json +++ b/static/schemas/v1/media-buy/build-creative-request.json @@ -15,14 +15,13 @@ "type": "string", "description": "Natural language description of the creative brief or requirements" }, - "format_source": { + "source_format_id": { "type": "string", - "description": "Source URL for format lookup (sales agent URL) or null for standard AdCP formats", - "format": "uri" + "description": "Format ID of existing creative to transform (optional - omit when creating from scratch). Supports namespaced IDs like 'display_300x250' (standard) or 'https://publisher.com/.well-known/adcp/sales:custom_format' (custom)" }, - "format_id": { - "type": "string", - "description": "Format identifier to look up from the format source" + "target_format_id": { + "type": "string", + "description": "Format ID to generate. Supports namespaced IDs like 'display_300x250' (standard) or 'https://publisher.com/.well-known/adcp/sales:custom_format' (custom). The format definition specifies whether output is manifest-based or code-based." }, "context_id": { "type": "string", @@ -35,12 +34,6 @@ "$ref": "/schemas/v1/core/creative-asset.json" } }, - "output_mode": { - "type": "string", - "enum": ["manifest", "code"], - "default": "manifest", - "description": "Output format - 'manifest' for asset-based creatives, 'code' for executable creative code" - }, "brand_manifest": { "$ref": "/schemas/v1/core/brand-manifest.json", "description": "Brand information manifest providing context for creative generation. Replaces brand_guidelines with a more comprehensive and standardized format." @@ -79,6 +72,6 @@ "additionalProperties": false } }, - "required": ["message", "format_id"], + "required": ["message", "target_format_id"], "additionalProperties": false } \ No newline at end of file diff --git a/static/schemas/v1/media-buy/sync-creatives-response.json b/static/schemas/v1/media-buy/sync-creatives-response.json index cbb75ba678..a769213d11 100644 --- a/static/schemas/v1/media-buy/sync-creatives-response.json +++ b/static/schemas/v1/media-buy/sync-creatives-response.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-07/schema#", "$id": "/schemas/v1/media-buy/sync-creatives-response.json", "title": "Sync Creatives Response", - "description": "Response from creative sync operation with detailed results and bulk operation summary", + "description": "Response from creative sync operation with results for each creative", "type": "object", "properties": { "adcp_version": { @@ -12,11 +12,11 @@ }, "message": { "type": "string", - "description": "Human-readable result message summarizing the sync operation" + "description": "Human-readable result message (e.g., 'Synced 3 creatives: 2 created, 1 updated')" }, "context_id": { "type": "string", - "description": "Context ID for tracking async operations" + "description": "Context ID for tracking async operations and conversational approval workflows" }, "status": { "$ref": "/schemas/v1/enums/task-status.json", @@ -31,47 +31,9 @@ "type": "boolean", "description": "Whether this was a dry run (no actual changes made)" }, - "summary": { - "type": "object", - "description": "High-level summary of sync operation results", - "properties": { - "total_processed": { - "type": "integer", - "description": "Total number of creatives processed", - "minimum": 0 - }, - "created": { - "type": "integer", - "description": "Number of new creatives created", - "minimum": 0 - }, - "updated": { - "type": "integer", - "description": "Number of existing creatives updated", - "minimum": 0 - }, - "unchanged": { - "type": "integer", - "description": "Number of creatives that were already up-to-date", - "minimum": 0 - }, - "failed": { - "type": "integer", - "description": "Number of creatives that failed validation or processing", - "minimum": 0 - }, - "deleted": { - "type": "integer", - "description": "Number of creatives deleted/archived (when delete_missing=true)", - "minimum": 0 - } - }, - "required": ["total_processed", "created", "updated", "unchanged", "failed"], - "additionalProperties": false - }, - "results": { + "creatives": { "type": "array", - "description": "Detailed results for each creative processed", + "description": "Results for each creative processed", "items": { "type": "object", "properties": { @@ -84,24 +46,20 @@ "enum": ["created", "updated", "unchanged", "failed", "deleted"], "description": "Action taken for this creative" }, - "status": { - "$ref": "/schemas/v1/enums/creative-status.json", - "description": "Current approval status of the creative" - }, "platform_id": { "type": "string", "description": "Platform-specific ID assigned to the creative" }, "changes": { "type": "array", - "description": "List of field names that were modified (for 'updated' action)", + "description": "Field names that were modified (only present when action='updated')", "items": { "type": "string" } }, "errors": { "type": "array", - "description": "Validation or processing errors (for 'failed' action)", + "description": "Validation or processing errors (only present when action='failed')", "items": { "type": "string" } @@ -113,170 +71,40 @@ "type": "string" } }, - "review_feedback": { - "type": "string", - "description": "Feedback from platform review process" - }, - "suggested_adaptations": { - "type": "array", - "description": "Recommended creative adaptations for better performance", - "items": { - "type": "object", - "properties": { - "adaptation_id": { - "type": "string", - "description": "Unique identifier for this adaptation" - }, - "format_id": { - "type": "string", - "description": "Target format ID for the adaptation" - }, - "name": { - "type": "string", - "description": "Suggested name for the adapted creative" - }, - "description": { - "type": "string", - "description": "What this adaptation does" - }, - "estimated_performance_lift": { - "type": "number", - "description": "Expected performance improvement (percentage)" - } - }, - "required": ["adaptation_id", "format_id", "name", "description"], - "additionalProperties": false - } - }, - "previews": { - "type": "array", - "description": "Preview URLs for generative creatives - one preview per input provided in request", - "items": { - "type": "object", - "properties": { - "preview_url": { - "type": "string", - "format": "uri", - "description": "URL to HTML page that renders this preview variant" - }, - "input": { - "type": "object", - "description": "Echo of input parameters used to generate this preview", - "properties": { - "name": { - "type": "string", - "description": "Name of this preview variant" - }, - "macros": { - "type": "object", - "description": "Macro values applied", - "additionalProperties": {"type": "string"} - }, - "context_description": { - "type": "string", - "description": "Context description applied" - } - }, - "required": ["name"], - "additionalProperties": false - } - }, - "required": ["preview_url", "input"], - "additionalProperties": false - } - }, - "interactive_url": { + "preview_url": { "type": "string", "format": "uri", - "description": "Optional URL to interactive testing page with controls to switch between preview variants" + "description": "Preview URL for generative creatives (only present for generative formats)" }, "expires_at": { "type": "string", "format": "date-time", - "description": "ISO 8601 timestamp when preview links expire" - } - }, - "required": ["creative_id", "action"], - "additionalProperties": false - } - }, - "assignments_summary": { - "type": "object", - "description": "Summary of assignment operations (when assignments were included in request)", - "properties": { - "total_assignments_processed": { - "type": "integer", - "description": "Total number of creative-package assignment operations processed", - "minimum": 0 - }, - "assigned": { - "type": "integer", - "description": "Number of successful creative-package assignments", - "minimum": 0 - }, - "unassigned": { - "type": "integer", - "description": "Number of creative-package unassignments", - "minimum": 0 - }, - "failed": { - "type": "integer", - "description": "Number of assignment operations that failed", - "minimum": 0 - } - }, - "required": ["total_assignments_processed", "assigned", "unassigned", "failed"], - "additionalProperties": false - }, - "assignment_results": { - "type": "array", - "description": "Detailed assignment results (when assignments were included in request)", - "items": { - "type": "object", - "properties": { - "creative_id": { - "type": "string", - "description": "Creative that was assigned/unassigned" - }, - "assigned_packages": { - "type": "array", - "description": "Packages successfully assigned to this creative", - "items": { - "type": "string" - } + "description": "ISO 8601 timestamp when preview link expires (only present when preview_url exists)" }, - "unassigned_packages": { + "assigned_to": { "type": "array", - "description": "Packages successfully unassigned from this creative", + "description": "Package IDs this creative was successfully assigned to (only present when assignments were requested)", "items": { "type": "string" } }, - "failed_packages": { - "type": "array", - "description": "Packages that failed to assign/unassign", - "items": { - "type": "object", - "properties": { - "package_id": { - "type": "string", - "description": "Package ID that failed" - }, - "error": { - "type": "string", - "description": "Error message for the failed assignment" - } - }, - "required": ["package_id", "error"], - "additionalProperties": false - } + "assignment_errors": { + "type": "object", + "description": "Assignment errors by package ID (only present when assignment failures occurred)", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "string", + "description": "Error message for this package assignment" + } + }, + "additionalProperties": false } }, - "required": ["creative_id"], + "required": ["creative_id", "action"], "additionalProperties": false } } }, - "required": ["adcp_version", "message", "status"], + "required": ["adcp_version", "message", "status", "creatives"], "additionalProperties": false } \ No newline at end of file From 150676b04f12305aafec8130ad109dc361f50b5f Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 14:38:44 -0400 Subject: [PATCH 06/11] fix: use consistent {agent_url:format_id} pattern for all format IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updated format ID documentation and examples to consistently use the {agent_url:format_id} namespacing pattern throughout. Standard formats now use: https://creatives.adcontextprotocol.org:display_native Custom formats use: https://publisher.com/.well-known/adcp/sales:custom_format This makes the format namespace explicit and consistent across all examples. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../creative/task-reference/build_creative.md | 44 ++++++++++--------- .../v1/media-buy/build-creative-request.json | 4 +- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/docs/creative/task-reference/build_creative.md b/docs/creative/task-reference/build_creative.md index 5db8e10dbd..6b18f378bf 100644 --- a/docs/creative/task-reference/build_creative.md +++ b/docs/creative/task-reference/build_creative.md @@ -9,31 +9,33 @@ Build creative content for a specific format using a creative agent that can gen ## Format Identification -Format IDs support namespacing to distinguish between standard and custom formats: +All format IDs use the `{agent_url:format_id}` namespacing pattern: -1. **Standard AdCP Formats**: Simple ID like `"display_native"` or `"video_standard_30s"` +1. **Standard AdCP Formats**: Use the standard creative agent URL + - Pattern: `https://creatives.adcontextprotocol.org` + `:` + `display_300x250` -2. **Publisher-Specific Formats**: Namespaced with URL like `"https://publisher.com/.well-known/adcp/sales:premium_video_30s"` +2. **Publisher-Specific Formats**: Use the publisher's sales agent URL + - Pattern: `https://publisher.com/.well-known/adcp/sales` + `:` + `premium_video_30s` -3. **Format Discovery**: Use `list_creative_formats` to discover available formats and their requirements +3. **Format Discovery**: Use `list_creative_formats` on the agent URL to discover available formats and their requirements ### Format ID Examples ```json // Standard AdCP format { - "target_format_id": "display_native" + "target_format_id": "https://creatives.adcontextprotocol.org\:display_native" } -// Publisher-specific format (namespaced) +// Publisher-specific format { - "target_format_id": "https://publisher.com/.well-known/adcp/sales:premium_video_30s" + "target_format_id": "https://publisher.com/.well-known/adcp/sales\:premium_video_30s" } // Transform existing creative to new format { - "source_format_id": "display_300x250", - "target_format_id": "display_native" + "source_format_id": "https://creatives.adcontextprotocol.org\:display_300x250", + "target_format_id": "https://creatives.adcontextprotocol.org\:display_native" } ``` @@ -42,8 +44,8 @@ Format IDs support namespacing to distinguish between standard and custom format | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `message` | string | Yes | The request message (initial brief or refinement instructions) | -| `source_format_id` | string | No | Format ID of existing creative to transform (optional - omit when creating from scratch). Supports namespaced IDs. | -| `target_format_id` | string | Yes | Format ID to generate (e.g., 'display_native', 'video_30s'). Supports namespaced IDs like 'https://publisher.com/.well-known/adcp/sales:custom_format'. The format definition specifies whether output is manifest-based or code-based. | +| `source_format_id` | string | No | Format ID of existing creative to transform (optional - omit when creating from scratch). Uses `{agent_url:format_id}` pattern (e.g., 'https://creatives.adcontextprotocol.org\:display_300x250'). | +| `target_format_id` | string | Yes | Format ID to generate. Uses `{agent_url:format_id}` pattern (e.g., 'https://creatives.adcontextprotocol.org\:display_native' or 'https://publisher.com/.well-known/adcp/sales\:custom_format'). The format definition specifies whether output is manifest-based or code-based. | | `context_id` | string | No | Session context from previous message for continuity | | `brand_manifest` | BrandManifest | No | Brand information manifest containing all assets, themes, and information necessary to ensure creatives are aligned with the brand's goals and that the publisher is comfortable with what's being advertised. See [Brand Manifest](../../reference/brand-manifest) for details. | | `assets` | array | No | References to asset libraries and specific assets | @@ -114,7 +116,7 @@ The format definition determines whether the output is a creative manifest (asse ```json { "type": "creative_manifest", - "target_format_id": "display_native", + "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", "assets": { "headline": "Premium Dog Nutrition", "description": "Veterinarian recommended formula with real salmon", @@ -145,7 +147,7 @@ The format definition determines whether the output is a creative manifest (asse ```json { "type": "creative_code", - "target_format_id": "html5", + "target_format_id": "https://creatives.adcontextprotocol.org\:html5", "code": "
\n \n
", "dependencies": { "context_required": ["weather", "time"], @@ -186,7 +188,7 @@ The format definition determines whether the output is a creative manifest (asse ```json { "message": "Create a native ad for Yahoo promoting Purina Pro Plan. Focus on the veterinarian recommendation and that real salmon is the #1 ingredient.", - "target_format_id": "display_native", + "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", "brand_manifest": { "url": "https://purina.com", "name": "Purina Pro Plan", @@ -218,7 +220,7 @@ The format definition determines whether the output is a creative manifest (asse ```json { "message": "Create a native ad for Yahoo promoting Purina Pro Plan. Focus on the veterinarian recommendation and that real salmon is the #1 ingredient. Use an informative and trustworthy tone with 'Learn More' as the CTA.", - "target_format_id": "display_native", + "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", "brand_manifest": { "url": "https://purina.com" }, @@ -239,7 +241,7 @@ The format definition determines whether the output is a creative manifest (asse "status": "draft", "creative_output": { "type": "creative_manifest", - "target_format_id": "display_native", + "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", "assets": { "headline": "Veterinarian Recommended Nutrition", "description": "Pro Plan with real salmon as the #1 ingredient provides complete nutrition for your dog's sensitive skin and stomach.", @@ -310,7 +312,7 @@ The format definition determines whether the output is a creative manifest (asse "status": "ready", "creative_output": { "type": "creative_manifest", - "target_format_id": "display_native", + "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", "assets": { "headline": "Veterinarian Recommended Grain-Free Nutrition", "description": "Pro Plan's grain-free formula with real salmon as the #1 ingredient provides complete nutrition without grains that can upset sensitive stomachs.", @@ -342,7 +344,7 @@ The format definition determines whether the output is a creative manifest (asse "status": "ready", "creative_output": { "type": "creative_manifest", - "target_format_id": "display_native", + "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", "assets": { "headline": "Veterinarian Recommended Grain-Free Nutrition", "description": "Pro Plan's grain-free formula with real salmon as the #1 ingredient provides complete nutrition without grains that can upset sensitive stomachs.", @@ -368,7 +370,7 @@ The format definition determines whether the output is a creative manifest (asse ```json { "message": "I need a dynamic 30-second video for Purina that adapts based on viewer context. It should be upbeat and personalized, focusing on premium nutrition tailored for each dog's needs. The CTA should be 'Find Your Formula'.", - "target_format_id": "video_standard_30s", + "target_format_id": "https://creatives.adcontextprotocol.org\:video_standard_30s", "assets": [ { "library_id": "purina_video_library", @@ -433,7 +435,7 @@ The format definition determines whether the output is a creative manifest (asse ```json { "message": "Create a short-form video ad featuring user-generated content style. Keep it authentic and fun, focusing on real pet parents and their transformation stories. Use 'See Their Story' as the CTA.", - "target_format_id": "custom_short_form_video", + "target_format_id": "https://publisher.com/.well-known/adcp/sales\:custom_short_form_video", "format_source": "https://videoplatform.com/.well-known/adcp/sales", } ``` @@ -443,7 +445,7 @@ The format definition determines whether the output is a creative manifest (asse { "context_id": "ctx-video-123", "creative": { - "target_format_id": "custom_short_form_video", + "target_format_id": "https://publisher.com/.well-known/adcp/sales\:custom_short_form_video", "format_source": "https://videoplatform.com/.well-known/adcp/sales", "id": "custom_short_form_video", "name": "Short Form Video Ad", diff --git a/static/schemas/v1/media-buy/build-creative-request.json b/static/schemas/v1/media-buy/build-creative-request.json index a585bee863..23cfe2fc6e 100644 --- a/static/schemas/v1/media-buy/build-creative-request.json +++ b/static/schemas/v1/media-buy/build-creative-request.json @@ -17,11 +17,11 @@ }, "source_format_id": { "type": "string", - "description": "Format ID of existing creative to transform (optional - omit when creating from scratch). Supports namespaced IDs like 'display_300x250' (standard) or 'https://publisher.com/.well-known/adcp/sales:custom_format' (custom)" + "description": "Format ID of existing creative to transform (optional - omit when creating from scratch). Uses {agent_url:format_id} pattern, e.g., 'https://creatives.adcontextprotocol.org:display_300x250' or 'https://publisher.com/.well-known/adcp/sales:custom_format'" }, "target_format_id": { "type": "string", - "description": "Format ID to generate. Supports namespaced IDs like 'display_300x250' (standard) or 'https://publisher.com/.well-known/adcp/sales:custom_format' (custom). The format definition specifies whether output is manifest-based or code-based." + "description": "Format ID to generate. Uses {agent_url:format_id} pattern, e.g., 'https://creatives.adcontextprotocol.org:display_native' or 'https://publisher.com/.well-known/adcp/sales:custom_format'. The format definition specifies whether output is manifest-based or code-based." }, "context_id": { "type": "string", From 37f4cd6533c09e3f7ce7f57620b7a2916ebb9d8d Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 14:47:34 -0400 Subject: [PATCH 07/11] refactor: structure format IDs as objects with agent_url and id fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed format IDs from delimited strings to structured objects to avoid parsing issues and provide clear, unambiguous namespacing. Before (string with delimiter): "target_format_id": "https://creatives.adcontextprotocol.org:display_300x250" After (structured object): "target_format_id": { "agent_url": "https://creatives.adcontextprotocol.org", "id": "display_300x250" } Benefits: - No delimiter parsing or escaping issues (especially in MDX) - Self-documenting structure - Easier to validate with JSON Schema - Clear separation of namespace and identifier - More extensible for future needs Changes: - Created /schemas/v1/core/format-id.json schema - Updated build_creative request to use structured format IDs - Updated creative-asset schema to reference format-id - Updated all documentation examples - Added guidance to CLAUDE.md about when to use structured vs string format IDs Note: Product responses still use string format_ids arrays for compactness. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 31 +++++++ .../creative/task-reference/build_creative.md | 87 ++++++++++++++----- static/schemas/v1/core/creative-asset.json | 4 +- static/schemas/v1/core/format-id.json | 21 +++++ .../v1/media-buy/build-creative-request.json | 8 +- 5 files changed, 123 insertions(+), 28 deletions(-) create mode 100644 static/schemas/v1/core/format-id.json diff --git a/CLAUDE.md b/CLAUDE.md index 2722f558bc..4dbb3c4c31 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -284,6 +284,37 @@ For major version changes: **Schema Validation**: All schemas must follow this convention. Tests will fail if format fields don't match the expected naming pattern. +### Format ID Structure + +**CRITICAL**: Format IDs are structured objects, not strings, to avoid parsing issues and provide clear namespacing. + +**Structured Format ID**: +```json +{ + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_300x250" +} +``` + +**When to use structured format IDs**: +- **Request parameters** that accept a format ID (e.g., `target_format_id` in `build_creative`) +- **Creative asset objects** that specify which format they conform to +- Anywhere a format is being **referenced for action** + +**When to use string format IDs**: +- **Product responses** listing supported formats (for compactness) +- **Format definition objects** themselves (they have separate `format_id` and `agent_url` fields) +- Lists where verbosity is a concern + +**Schema reference**: +```json +{ + "target_format_id": { + "$ref": "/schemas/v1/core/format-id.json" + } +} +``` + ## Common Tasks ### Before Making Changes diff --git a/docs/creative/task-reference/build_creative.md b/docs/creative/task-reference/build_creative.md index 6b18f378bf..cbec2ec19f 100644 --- a/docs/creative/task-reference/build_creative.md +++ b/docs/creative/task-reference/build_creative.md @@ -9,33 +9,46 @@ Build creative content for a specific format using a creative agent that can gen ## Format Identification -All format IDs use the `{agent_url:format_id}` namespacing pattern: +Format IDs are structured objects with two fields: -1. **Standard AdCP Formats**: Use the standard creative agent URL - - Pattern: `https://creatives.adcontextprotocol.org` + `:` + `display_300x250` +1. **agent_url**: The URL of the agent that defines the format + - Standard formats: `https://creatives.adcontextprotocol.org` + - Custom formats: `https://publisher.com/.well-known/adcp/sales` -2. **Publisher-Specific Formats**: Use the publisher's sales agent URL - - Pattern: `https://publisher.com/.well-known/adcp/sales` + `:` + `premium_video_30s` +2. **id**: The format identifier within that agent's namespace + - Examples: `display_300x250`, `video_standard_30s`, `premium_video_30s` -3. **Format Discovery**: Use `list_creative_formats` on the agent URL to discover available formats and their requirements +3. **Format Discovery**: Use `list_creative_formats` on the agent URL to discover available formats ### Format ID Examples ```json // Standard AdCP format { - "target_format_id": "https://creatives.adcontextprotocol.org\:display_native" + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_native" + } } // Publisher-specific format { - "target_format_id": "https://publisher.com/.well-known/adcp/sales\:premium_video_30s" + "target_format_id": { + "agent_url": "https://publisher.com/.well-known/adcp/sales", + "id": "premium_video_30s" + } } // Transform existing creative to new format { - "source_format_id": "https://creatives.adcontextprotocol.org\:display_300x250", - "target_format_id": "https://creatives.adcontextprotocol.org\:display_native" + "source_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_300x250" + }, + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_native" + } } ``` @@ -44,8 +57,8 @@ All format IDs use the `{agent_url:format_id}` namespacing pattern: | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `message` | string | Yes | The request message (initial brief or refinement instructions) | -| `source_format_id` | string | No | Format ID of existing creative to transform (optional - omit when creating from scratch). Uses `{agent_url:format_id}` pattern (e.g., 'https://creatives.adcontextprotocol.org\:display_300x250'). | -| `target_format_id` | string | Yes | Format ID to generate. Uses `{agent_url:format_id}` pattern (e.g., 'https://creatives.adcontextprotocol.org\:display_native' or 'https://publisher.com/.well-known/adcp/sales\:custom_format'). The format definition specifies whether output is manifest-based or code-based. | +| `source_format_id` | object | No | Format ID of existing creative to transform (optional - omit when creating from scratch). Object with `agent_url` and `id` fields. | +| `target_format_id` | object | Yes | Format ID to generate. Object with `agent_url` and `id` fields. The format definition specifies whether output is manifest-based or code-based. | | `context_id` | string | No | Session context from previous message for continuity | | `brand_manifest` | BrandManifest | No | Brand information manifest containing all assets, themes, and information necessary to ensure creatives are aligned with the brand's goals and that the publisher is comfortable with what's being advertised. See [Brand Manifest](../../reference/brand-manifest) for details. | | `assets` | array | No | References to asset libraries and specific assets | @@ -116,7 +129,10 @@ The format definition determines whether the output is a creative manifest (asse ```json { "type": "creative_manifest", - "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_native" + }, "assets": { "headline": "Premium Dog Nutrition", "description": "Veterinarian recommended formula with real salmon", @@ -147,7 +163,10 @@ The format definition determines whether the output is a creative manifest (asse ```json { "type": "creative_code", - "target_format_id": "https://creatives.adcontextprotocol.org\:html5", + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "html5" + }, "code": "
\n \n
", "dependencies": { "context_required": ["weather", "time"], @@ -188,7 +207,10 @@ The format definition determines whether the output is a creative manifest (asse ```json { "message": "Create a native ad for Yahoo promoting Purina Pro Plan. Focus on the veterinarian recommendation and that real salmon is the #1 ingredient.", - "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_native" + }, "brand_manifest": { "url": "https://purina.com", "name": "Purina Pro Plan", @@ -220,7 +242,10 @@ The format definition determines whether the output is a creative manifest (asse ```json { "message": "Create a native ad for Yahoo promoting Purina Pro Plan. Focus on the veterinarian recommendation and that real salmon is the #1 ingredient. Use an informative and trustworthy tone with 'Learn More' as the CTA.", - "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_native" + }, "brand_manifest": { "url": "https://purina.com" }, @@ -241,7 +266,10 @@ The format definition determines whether the output is a creative manifest (asse "status": "draft", "creative_output": { "type": "creative_manifest", - "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_native" + }, "assets": { "headline": "Veterinarian Recommended Nutrition", "description": "Pro Plan with real salmon as the #1 ingredient provides complete nutrition for your dog's sensitive skin and stomach.", @@ -312,7 +340,10 @@ The format definition determines whether the output is a creative manifest (asse "status": "ready", "creative_output": { "type": "creative_manifest", - "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_native" + }, "assets": { "headline": "Veterinarian Recommended Grain-Free Nutrition", "description": "Pro Plan's grain-free formula with real salmon as the #1 ingredient provides complete nutrition without grains that can upset sensitive stomachs.", @@ -344,7 +375,10 @@ The format definition determines whether the output is a creative manifest (asse "status": "ready", "creative_output": { "type": "creative_manifest", - "target_format_id": "https://creatives.adcontextprotocol.org\:display_native", + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "display_native" + }, "assets": { "headline": "Veterinarian Recommended Grain-Free Nutrition", "description": "Pro Plan's grain-free formula with real salmon as the #1 ingredient provides complete nutrition without grains that can upset sensitive stomachs.", @@ -370,7 +404,10 @@ The format definition determines whether the output is a creative manifest (asse ```json { "message": "I need a dynamic 30-second video for Purina that adapts based on viewer context. It should be upbeat and personalized, focusing on premium nutrition tailored for each dog's needs. The CTA should be 'Find Your Formula'.", - "target_format_id": "https://creatives.adcontextprotocol.org\:video_standard_30s", + "target_format_id": { + "agent_url": "https://creatives.adcontextprotocol.org", + "id": "video_standard_30s" + }, "assets": [ { "library_id": "purina_video_library", @@ -435,7 +472,10 @@ The format definition determines whether the output is a creative manifest (asse ```json { "message": "Create a short-form video ad featuring user-generated content style. Keep it authentic and fun, focusing on real pet parents and their transformation stories. Use 'See Their Story' as the CTA.", - "target_format_id": "https://publisher.com/.well-known/adcp/sales\:custom_short_form_video", + "target_format_id": { + "agent_url": "https://publisher.com/.well-known/adcp/sales", + "id": "custom_short_form_video" + }, "format_source": "https://videoplatform.com/.well-known/adcp/sales", } ``` @@ -445,7 +485,10 @@ The format definition determines whether the output is a creative manifest (asse { "context_id": "ctx-video-123", "creative": { - "target_format_id": "https://publisher.com/.well-known/adcp/sales\:custom_short_form_video", + "target_format_id": { + "agent_url": "https://publisher.com/.well-known/adcp/sales", + "id": "custom_short_form_video" + }, "format_source": "https://videoplatform.com/.well-known/adcp/sales", "id": "custom_short_form_video", "name": "Short Form Video Ad", diff --git a/static/schemas/v1/core/creative-asset.json b/static/schemas/v1/core/creative-asset.json index a8c42d0024..7530f5db14 100644 --- a/static/schemas/v1/core/creative-asset.json +++ b/static/schemas/v1/core/creative-asset.json @@ -14,8 +14,8 @@ "description": "Human-readable creative name" }, "format_id": { - "type": "string", - "description": "Format identifier (e.g., display_300x250_static, display_300x250_generative)" + "$ref": "/schemas/v1/core/format-id.json", + "description": "Format identifier specifying which format this creative conforms to" }, "assets": { "type": "object", diff --git a/static/schemas/v1/core/format-id.json b/static/schemas/v1/core/format-id.json new file mode 100644 index 0000000000..5469282f37 --- /dev/null +++ b/static/schemas/v1/core/format-id.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/format-id.json", + "title": "Format ID", + "description": "Structured format identifier with agent URL and format name", + "type": "object", + "properties": { + "agent_url": { + "type": "string", + "format": "uri", + "description": "URL of the agent that defines this format (e.g., 'https://creatives.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats)" + }, + "id": { + "type": "string", + "pattern": "^[a-zA-Z0-9_-]+$", + "description": "Format identifier within the agent's namespace (e.g., 'display_300x250', 'video_standard_30s')" + } + }, + "required": ["agent_url", "id"], + "additionalProperties": false +} diff --git a/static/schemas/v1/media-buy/build-creative-request.json b/static/schemas/v1/media-buy/build-creative-request.json index 23cfe2fc6e..b44a133dd0 100644 --- a/static/schemas/v1/media-buy/build-creative-request.json +++ b/static/schemas/v1/media-buy/build-creative-request.json @@ -16,12 +16,12 @@ "description": "Natural language description of the creative brief or requirements" }, "source_format_id": { - "type": "string", - "description": "Format ID of existing creative to transform (optional - omit when creating from scratch). Uses {agent_url:format_id} pattern, e.g., 'https://creatives.adcontextprotocol.org:display_300x250' or 'https://publisher.com/.well-known/adcp/sales:custom_format'" + "$ref": "/schemas/v1/core/format-id.json", + "description": "Format ID of existing creative to transform (optional - omit when creating from scratch)" }, "target_format_id": { - "type": "string", - "description": "Format ID to generate. Uses {agent_url:format_id} pattern, e.g., 'https://creatives.adcontextprotocol.org:display_native' or 'https://publisher.com/.well-known/adcp/sales:custom_format'. The format definition specifies whether output is manifest-based or code-based." + "$ref": "/schemas/v1/core/format-id.json", + "description": "Format ID to generate. The format definition specifies whether output is manifest-based or code-based." }, "context_id": { "type": "string", From 0a8e812d5dc9d6d9c6088294b82fa47ab98f87b1 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 14:54:51 -0400 Subject: [PATCH 08/11] docs: add generative creative workflow to sync_creatives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added comprehensive documentation explaining how sync_creatives supports generative formats where the publisher creates the creative (AI or human) based on a brand manifest and generation prompt. Key additions: - New "Generative Creative Workflow" section explaining implementation-agnostic approach - Workflow steps showing async submission → creation → preview → approval - Example formats showing AI (fast), human (slow), and hybrid approaches - Example 4 demonstrating full generative creative submission with async responses Core insight: Buyer submits the same request whether publisher uses AI, human designers, or hybrid approach. The async workflow handles timing naturally, and preview URLs enable approval regardless of creation method. This enables publishers to offer premium bespoke creative services through the same protocol as AI-generated creatives. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../task-reference/sync_creatives.md | 116 +++++++++++++++++- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/docs/media-buy/task-reference/sync_creatives.md b/docs/media-buy/task-reference/sync_creatives.md index 5c11365e7c..8940f552c3 100644 --- a/docs/media-buy/task-reference/sync_creatives.md +++ b/docs/media-buy/task-reference/sync_creatives.md @@ -16,9 +16,10 @@ The `sync_creatives` task provides a powerful, efficient approach to creative li - **Bulk Operations**: Process up to 100 creatives per request - **Upsert Semantics**: Automatic create-or-update behavior - **Patch Mode**: Update only specified fields while preserving others -- **Assignment Management**: Bulk assign creatives to packages in the same request +- **Assignment Management**: Bulk assign creatives to packages in the same request - **Validation Modes**: Choose between strict (fail-fast) or lenient (process-valid) validation - **Dry Run**: Preview changes before applying them +- **Generative Creatives**: Submit brand manifest and generation prompts for AI or human-created creatives - **Comprehensive Reporting**: Detailed results for each creative processed ## Request Parameters @@ -56,11 +57,17 @@ Each creative in the `creatives` array follows the [Creative Asset schema](/sche - `click_url`, `width`, `height`, `duration`, `tags` (optional) **Native Ad Templates:** -- `creative_id`, `name`, `format` (required) +- `creative_id`, `name`, `format_id` (required) - `snippet` (HTML template with variables like `[%Headline%]`) - `snippet_type: "html"` - `assets` array with sub-assets for template variables +**Generative Creatives:** +- `creative_id`, `name`, `format_id` (required - references a generative format) +- `assets` object with `brand_manifest` and `generation_prompt` +- Publisher creates the creative (AI or human - buyer doesn't know or care) +- Response includes `preview_url` when ready for review + ## Webhook Configuration (Task-Specific) For large bulk operations or creative approval workflows, you can provide a task-specific webhook to be notified when the sync completes: @@ -119,6 +126,34 @@ The response provides comprehensive details about the sync operation: } ``` +## Generative Creative Workflow + +For generative formats, buyers submit a creative manifest (brand information + generation instructions) rather than finished assets. The publisher then creates the creative - either through AI generation, human designers, or a hybrid approach. + +**Key Characteristics:** + +1. **Implementation Agnostic**: Buyer submits the same request whether the publisher uses AI, human designers, or both +2. **Async by Default**: Response time ranges from seconds (AI) to days (human review) +3. **Preview-First**: Publisher returns `preview_url` for buyer approval before campaign launch +4. **Conversational Refinement**: Buyer can request changes using the `context_id` from the response + +**Workflow Steps:** + +``` +1. Buyer submits creative with brand_manifest + generation_prompt +2. Publisher responds with status: "submitted" or "working" +3. Publisher creates creative (AI/human/hybrid - buyer doesn't know) +4. Publisher responds with status: "completed" + preview_url +5. Buyer reviews preview and approves or requests changes +``` + +**Example Generative Formats:** +- `premium_bespoke_display` - Custom-designed display ad (human designer, 24-48h) +- `ai_native_responsive` - AI-generated native ad (automated, under 60s) +- `hybrid_video_30s` - AI draft + human polish (hybrid, 2-4h) + +The format definition specifies expected turnaround time, but the buyer's workflow is identical regardless of implementation. + ## Usage Examples ### Example 1: Full Sync with New Creatives @@ -213,7 +248,80 @@ Upload a native ad template with variable substitution: } ``` -### Example 4: Dry Run Preview +### Example 4: Generative Creative Submission + +Submit a creative manifest for publisher to create (AI or human): + +**Request:** +```json +{ + "creatives": [ + { + "creative_id": "holiday_hero_display", + "name": "Holiday Campaign Hero Display", + "format_id": { + "agent_url": "https://publisher.com/.well-known/adcp/sales", + "id": "premium_bespoke_display" + }, + "assets": { + "brand_manifest": { + "asset_type": "brand_manifest", + "url": "https://retailer.com", + "colors": { + "primary": "#C41E3A", + "secondary": "#165B33", + "accent": "#FFD700" + }, + "tone": "Warm, festive, family-oriented" + }, + "generation_prompt": { + "asset_type": "text", + "content": "Create a holiday campaign featuring our winter collection. Emphasize warmth, family togetherness, and quality. Include subtle holiday elements without being overtly religious." + } + }, + "tags": ["holiday", "q4_2024", "hero"] + } + ] +} +``` + +**Initial Response (Async):** +```json +{ + "status": "submitted", + "message": "Creative submitted for creation", + "context_id": "ctx_abc123", + "creatives": [ + { + "creative_id": "holiday_hero_display", + "action": "created", + "platform_id": "pub_creative_789" + } + ] +} +``` + +**Later Response (When Ready):** +```json +{ + "status": "completed", + "message": "Creative ready for review", + "context_id": "ctx_abc123", + "creatives": [ + { + "creative_id": "holiday_hero_display", + "action": "created", + "platform_id": "pub_creative_789", + "preview_url": "https://publisher.com/preview/pub_creative_789", + "expires_at": "2024-12-20T00:00:00Z" + } + ] +} +``` + +The buyer doesn't know if this creative was generated by AI in 20 seconds or designed by a human team over 2 days. The workflow is identical either way. + +### Example 5: Dry Run Preview Preview changes before applying them: @@ -232,7 +340,7 @@ Preview changes before applying them: } ``` -### Example 5: Library Replacement (Advanced) +### Example 6: Library Replacement (Advanced) Replace entire creative library with new set (use with extreme caution): From 5d1ab7b1156425f8551ceac2e4eac177472da301 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 15:14:35 -0400 Subject: [PATCH 09/11] refactor: simplify schemas based on PR feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major schema simplifications addressing PR comments #5-14: **Simplified Approval Workflow (Comment #14):** - Removed sync-creatives-approval-request.json schema - Added `approved` boolean field to creative-asset - Added `context_id` to sync-creatives-request for conversational refinement - Buyers can now approve/refine by setting approved=true/false in creatives array **Renamed brand_manifest → promoted_offerings (Comment #13):** - Updated asset-type.json enum - Updated creative-asset.json asset_type enum - More accurate name since it includes products/offerings, not just brand info **Enhanced promoted-offerings Schema (Comments #9 & 10):** - Added `offerings` array for inline offerings without product catalog - Each offering has name, description, and assets array - Moved `additional_assets` from promoted-products to promoted-offerings - Supports both catalog-based (product_selectors) and inline (offerings) approaches **Consolidated Parameters (Comments #11 & 12):** - preview_creative: Replaced brand_manifest + promoted_products + asset_filters with single promoted_offerings parameter - build_creative: Removed brand_manifest, promoted_products, asset_filters - should be in creative manifest assets - Cleaner API surface, less redundancy **Removed promoted_products from create_media_buy (Comment #5):** - Per user feedback: products can be picked up from creatives - Walmart showing mac & cheese ad doesn't need to specify products at media buy level - Brand manifest represents the advertiser (Walmart), promoted products vary by creative 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- static/schemas/v1/core/asset-type.json | 2 +- static/schemas/v1/core/creative-asset.json | 14 ++- .../schemas/v1/core/promoted-offerings.json | 30 ++++- static/schemas/v1/core/promoted-products.json | 9 -- .../v1/creative/preview-creative-request.json | 39 +----- .../v1/media-buy/build-creative-request.json | 39 +----- .../media-buy/create-media-buy-request.json | 6 +- .../sync-creatives-approval-request.json | 115 ------------------ .../v1/media-buy/sync-creatives-request.json | 4 + 9 files changed, 48 insertions(+), 210 deletions(-) delete mode 100644 static/schemas/v1/media-buy/sync-creatives-approval-request.json diff --git a/static/schemas/v1/core/asset-type.json b/static/schemas/v1/core/asset-type.json index 1108967676..608429c877 100644 --- a/static/schemas/v1/core/asset-type.json +++ b/static/schemas/v1/core/asset-type.json @@ -11,7 +11,7 @@ }, "type": { "type": "string", - "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "brand_manifest", "url"], + "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "promoted_offerings", "url"], "description": "Type of asset" }, "required": { diff --git a/static/schemas/v1/core/creative-asset.json b/static/schemas/v1/core/creative-asset.json index 7530f5db14..1459230247 100644 --- a/static/schemas/v1/core/creative-asset.json +++ b/static/schemas/v1/core/creative-asset.json @@ -27,13 +27,13 @@ "properties": { "asset_type": { "type": "string", - "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "brand_manifest", "url"], + "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "promoted_offerings", "url"], "description": "Type of this asset" }, "url": { "type": "string", "format": "uri", - "description": "URL for image/video/audio assets or brand_manifest URL" + "description": "URL for image/video/audio assets or promoted_offerings URL reference" }, "content": { "type": "string", @@ -60,7 +60,7 @@ }, "colors": { "type": "object", - "description": "Brand colors (for brand_manifest asset type)", + "description": "Brand colors (for promoted_offerings asset type)", "properties": { "primary": {"type": "string"}, "secondary": {"type": "string"}, @@ -69,12 +69,12 @@ }, "fonts": { "type": "array", - "description": "Brand fonts (for brand_manifest asset type)", + "description": "Brand fonts (for promoted_offerings asset type)", "items": {"type": "string"} }, "tone": { "type": "string", - "description": "Brand tone/voice (for brand_manifest asset type)" + "description": "Brand tone/voice (for promoted_offerings asset type)" } }, "required": ["asset_type"], @@ -113,6 +113,10 @@ "items": { "type": "string" } + }, + "approved": { + "type": "boolean", + "description": "For generative creatives: set to true to approve and finalize, false to request regeneration with updated assets/message. Omit for non-generative creatives." } }, "required": ["creative_id", "name", "format_id", "assets"], diff --git a/static/schemas/v1/core/promoted-offerings.json b/static/schemas/v1/core/promoted-offerings.json index 3b654b014a..c6eb8f2b36 100644 --- a/static/schemas/v1/core/promoted-offerings.json +++ b/static/schemas/v1/core/promoted-offerings.json @@ -11,7 +11,35 @@ }, "product_selectors": { "$ref": "/schemas/v1/core/promoted-products.json", - "description": "Selectors to choose which products/offerings from the brand manifest to promote" + "description": "Selectors to choose which products/offerings from the brand manifest product catalog to promote" + }, + "offerings": { + "type": "array", + "description": "Inline offerings for campaigns without a product catalog. Each offering has a name, description, and associated assets.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Offering name (e.g., 'Winter Sale', 'New Product Launch')" + }, + "description": { + "type": "string", + "description": "Description of what's being offered" + }, + "assets": { + "type": "array", + "description": "Assets specific to this offering", + "items": { + "type": "object", + "description": "Asset definition using standard asset structure", + "additionalProperties": true + } + } + }, + "required": ["name"], + "additionalProperties": false + } }, "asset_selectors": { "type": "object", diff --git a/static/schemas/v1/core/promoted-products.json b/static/schemas/v1/core/promoted-products.json index 24df8bffaa..48ab44e0c9 100644 --- a/static/schemas/v1/core/promoted-products.json +++ b/static/schemas/v1/core/promoted-products.json @@ -26,15 +26,6 @@ "manifest_query": { "type": "string", "description": "Natural language query to select products from the brand manifest (e.g., 'all Kraft Heinz pasta sauces', 'organic products under $20')" - }, - "additional_assets": { - "type": "array", - "description": "Campaign-specific assets to supplement brand manifest assets for promoted products", - "items": { - "type": "object", - "description": "Additional asset definition - uses standard asset structure", - "additionalProperties": true - } } }, "additionalProperties": false, diff --git a/static/schemas/v1/creative/preview-creative-request.json b/static/schemas/v1/creative/preview-creative-request.json index f213534025..294eea9d0b 100644 --- a/static/schemas/v1/creative/preview-creative-request.json +++ b/static/schemas/v1/creative/preview-creative-request.json @@ -49,42 +49,9 @@ "type": "string", "description": "Specific template ID for custom format rendering" }, - "brand_manifest": { - "$ref": "/schemas/v1/core/brand-manifest.json", - "description": "Brand information manifest providing context for preview generation. Used when creative manifest includes dynamic elements that need brand context." - }, - "promoted_products": { - "$ref": "/schemas/v1/core/promoted-products.json", - "description": "Products or offerings being promoted in this creative. Provides product-specific context for dynamic creative previews." - }, - "asset_filters": { - "type": "object", - "description": "Filters to select specific assets from the brand manifest's asset library for preview variants", - "properties": { - "tags": { - "type": "array", - "description": "Select assets matching any of these tags (e.g., ['holiday', 'winter'])", - "items": { - "type": "string" - } - }, - "asset_types": { - "type": "array", - "description": "Limit to specific asset types", - "items": { - "type": "string", - "enum": ["image", "video", "audio", "text"] - } - }, - "exclude_tags": { - "type": "array", - "description": "Exclude assets with these tags", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false + "promoted_offerings": { + "$ref": "/schemas/v1/core/promoted-offerings.json", + "description": "Complete offering specification for dynamic creative previews - includes brand manifest, product selectors, inline offerings, and asset filters" } }, "required": ["format_id", "creative_manifest"], diff --git a/static/schemas/v1/media-buy/build-creative-request.json b/static/schemas/v1/media-buy/build-creative-request.json index b44a133dd0..942fe1715d 100644 --- a/static/schemas/v1/media-buy/build-creative-request.json +++ b/static/schemas/v1/media-buy/build-creative-request.json @@ -29,47 +29,10 @@ }, "assets": { "type": "array", - "description": "Available assets to use in creative generation", + "description": "Available assets to use in creative generation - this is where promoted_offerings should be included as an asset", "items": { "$ref": "/schemas/v1/core/creative-asset.json" } - }, - "brand_manifest": { - "$ref": "/schemas/v1/core/brand-manifest.json", - "description": "Brand information manifest providing context for creative generation. Replaces brand_guidelines with a more comprehensive and standardized format." - }, - "promoted_products": { - "$ref": "/schemas/v1/core/promoted-products.json", - "description": "Products or offerings being promoted in this creative. Supports SKU selection from brand manifest's product catalog, or inline offerings for non-commerce campaigns." - }, - "asset_filters": { - "type": "object", - "description": "Filters to select specific assets from the brand manifest's asset library", - "properties": { - "tags": { - "type": "array", - "description": "Select assets matching any of these tags (e.g., ['holiday', 'winter'])", - "items": { - "type": "string" - } - }, - "asset_types": { - "type": "array", - "description": "Limit to specific asset types", - "items": { - "type": "string", - "enum": ["image", "video", "audio", "text"] - } - }, - "exclude_tags": { - "type": "array", - "description": "Exclude assets with these tags", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false } }, "required": ["message", "target_format_id"], diff --git a/static/schemas/v1/media-buy/create-media-buy-request.json b/static/schemas/v1/media-buy/create-media-buy-request.json index 3e4f0e7094..1285ff1137 100644 --- a/static/schemas/v1/media-buy/create-media-buy-request.json +++ b/static/schemas/v1/media-buy/create-media-buy-request.json @@ -28,11 +28,7 @@ }, "promoted_offering": { "type": "string", - "description": "DEPRECATED: Use brand_manifest with promoted_products instead. Legacy field for describing what is being promoted." - }, - "promoted_products": { - "$ref": "/schemas/v1/core/promoted-products.json", - "description": "Products or offerings being promoted in this media buy. Supports SKU selection from brand manifest's product catalog, or inline offerings for non-commerce campaigns." + "description": "DEPRECATED: Use brand_manifest instead. Legacy field for describing what is being promoted." }, "po_number": { "type": "string", diff --git a/static/schemas/v1/media-buy/sync-creatives-approval-request.json b/static/schemas/v1/media-buy/sync-creatives-approval-request.json deleted file mode 100644 index a800146d8a..0000000000 --- a/static/schemas/v1/media-buy/sync-creatives-approval-request.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "/schemas/v1/media-buy/sync-creatives-approval-request.json", - "title": "Sync Creatives Approval Request", - "description": "Approve or refine generative creatives after reviewing previews. Uses context_id from initial sync_creatives response to continue the conversation.", - "type": "object", - "properties": { - "adcp_version": { - "type": "string", - "description": "AdCP schema version for this request", - "pattern": "^\\d+\\.\\d+\\.\\d+$", - "default": "1.6.0" - }, - "context_id": { - "type": "string", - "description": "Context ID from the initial sync_creatives response that returned previews" - }, - "approve": { - "type": "array", - "description": "Array of creative_ids to approve and save to library", - "items": { - "type": "string" - } - }, - "refine": { - "type": "array", - "description": "Array of creatives to regenerate with new instructions", - "items": { - "type": "object", - "properties": { - "creative_id": { - "type": "string", - "description": "Creative ID to refine" - }, - "assets": { - "type": "object", - "description": "Updated assets (typically just generation_prompt with new message)", - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "type": "object", - "properties": { - "asset_type": { - "type": "string", - "enum": ["text", "brand_manifest"] - }, - "content": { - "type": "string", - "description": "New message/prompt for regeneration" - }, - "url": { - "type": "string", - "format": "uri", - "description": "Updated brand manifest URL" - } - }, - "required": ["asset_type"] - } - } - }, - "inputs": { - "type": "array", - "description": "Optional: New preview contexts to test with refined creative", - "items": { - "type": "object", - "properties": { - "name": {"type": "string"}, - "macros": { - "type": "object", - "additionalProperties": {"type": "string"} - }, - "context_description": {"type": "string"} - }, - "required": ["name"] - } - } - }, - "required": ["creative_id", "assets"], - "additionalProperties": false - } - } - }, - "required": ["context_id"], - "anyOf": [ - {"required": ["approve"]}, - {"required": ["refine"]} - ], - "additionalProperties": false, - "examples": [ - { - "description": "Approve one creative, refine another", - "data": { - "context_id": "ctx_abc123", - "approve": ["banner_001"], - "refine": [ - { - "creative_id": "banner_002", - "assets": { - "generation_prompt": { - "asset_type": "text", - "content": "Make it more vibrant with a stronger CTA" - } - } - } - ] - } - }, - { - "description": "Approve all creatives", - "data": { - "context_id": "ctx_abc123", - "approve": ["banner_001", "banner_002", "banner_003"] - } - } - ] -} diff --git a/static/schemas/v1/media-buy/sync-creatives-request.json b/static/schemas/v1/media-buy/sync-creatives-request.json index a6bfc20dd3..0be8334307 100644 --- a/static/schemas/v1/media-buy/sync-creatives-request.json +++ b/static/schemas/v1/media-buy/sync-creatives-request.json @@ -11,6 +11,10 @@ "pattern": "^\\d+\\.\\d+\\.\\d+$", "default": "1.6.0" }, + "context_id": { + "type": "string", + "description": "Context ID from a previous sync_creatives response. Used for conversational refinement of generative creatives - allows approving or requesting changes to previously generated creatives." + }, "creatives": { "type": "array", "description": "Array of creative assets to sync (create or update)", From e021ea79c2ba9fc132b67b873c12e04fa9b0daec Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 15:25:09 -0400 Subject: [PATCH 10/11] docs: address PR feedback comments #1, #6, and approval workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Comment #1: Centralize Format ID documentation** - Moved format ID structure docs from build_creative.md to formats.md - Added "Referencing Formats" section to central formats documentation - Explains structured format IDs and when strings are used - build_creative now links to central format docs **Comment #6: Clarify buyer sensitivity to AI vs human** - Updated wording: "may not know" instead of "doesn't know or care" - Added note that some buyers care about creation method for brand safety/compliance - Publishers should communicate approach during format discovery **Simplified Approval Workflow documentation:** - Updated generative workflow docs to show approved field usage - Added example showing approval (approved: true) - Added example showing refinement (approved: false + updated prompt) - Clarified that context_id enables conversational refinement - Removed references to deleted sync-creatives-approval-request schema **Other improvements:** - Updated asset type references from brand_manifest to promoted_offerings - Clarified workflow steps with approval/refinement options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/creative/formats.md | 31 +++++++++++ .../creative/task-reference/build_creative.md | 45 +--------------- .../task-reference/sync_creatives.md | 52 ++++++++++++++++--- 3 files changed, 77 insertions(+), 51 deletions(-) diff --git a/docs/creative/formats.md b/docs/creative/formats.md index e9446ef135..05b6ba63ad 100644 --- a/docs/creative/formats.md +++ b/docs/creative/formats.md @@ -82,6 +82,37 @@ The creative agent at that URL is the definitive source for: Buyers query the agent_url for full format details, validation, and preview capabilities. +## Referencing Formats + +When referencing a format in API requests (e.g., in `sync_creatives`, `build_creative`), use a structured format ID object that combines the agent URL and format identifier: + +```json +{ + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "display_300x250" + } +} +``` + +**Why structured objects?** +- **No parsing needed**: Components are explicit +- **Unambiguous**: Clear separation of namespace and identifier +- **Extensible**: Can add version or other metadata later +- **Validation-friendly**: Easy to validate with JSON Schema + +**When formats are referenced as strings:** +In some contexts like product responses, format IDs appear as simple strings for compactness: + +```json +{ + "product_id": "ctv_premium", + "format_ids": ["video_30s_hosted", "video_15s_hosted"] +} +``` + +These strings are shorthand references. To get the full format specification, use `list_creative_formats` which returns the complete format objects with `agent_url` and all details. + ## Format Structure Formats are JSON objects with the following key fields: diff --git a/docs/creative/task-reference/build_creative.md b/docs/creative/task-reference/build_creative.md index cbec2ec19f..79b9d9029c 100644 --- a/docs/creative/task-reference/build_creative.md +++ b/docs/creative/task-reference/build_creative.md @@ -7,50 +7,7 @@ sidebar_position: 13 Build creative content for a specific format using a creative agent that can generate either a creative manifest (static mode) or executable code (dynamic mode). This tool supports conversational refinement through a series of messages. -## Format Identification - -Format IDs are structured objects with two fields: - -1. **agent_url**: The URL of the agent that defines the format - - Standard formats: `https://creatives.adcontextprotocol.org` - - Custom formats: `https://publisher.com/.well-known/adcp/sales` - -2. **id**: The format identifier within that agent's namespace - - Examples: `display_300x250`, `video_standard_30s`, `premium_video_30s` - -3. **Format Discovery**: Use `list_creative_formats` on the agent URL to discover available formats - -### Format ID Examples - -```json -// Standard AdCP format -{ - "target_format_id": { - "agent_url": "https://creatives.adcontextprotocol.org", - "id": "display_native" - } -} - -// Publisher-specific format -{ - "target_format_id": { - "agent_url": "https://publisher.com/.well-known/adcp/sales", - "id": "premium_video_30s" - } -} - -// Transform existing creative to new format -{ - "source_format_id": { - "agent_url": "https://creatives.adcontextprotocol.org", - "id": "display_300x250" - }, - "target_format_id": { - "agent_url": "https://creatives.adcontextprotocol.org", - "id": "display_native" - } -} -``` +For information about format IDs and how to reference formats, see [Creative Formats - Referencing Formats](../formats.md#referencing-formats). ## Request Parameters diff --git a/docs/media-buy/task-reference/sync_creatives.md b/docs/media-buy/task-reference/sync_creatives.md index 8940f552c3..1dfe87bfcf 100644 --- a/docs/media-buy/task-reference/sync_creatives.md +++ b/docs/media-buy/task-reference/sync_creatives.md @@ -64,9 +64,10 @@ Each creative in the `creatives` array follows the [Creative Asset schema](/sche **Generative Creatives:** - `creative_id`, `name`, `format_id` (required - references a generative format) -- `assets` object with `brand_manifest` and `generation_prompt` -- Publisher creates the creative (AI or human - buyer doesn't know or care) +- `assets` object with `promoted_offerings` and `generation_prompt` +- Publisher creates the creative (AI or human - buyer may not know the implementation method) - Response includes `preview_url` when ready for review +- Note: Some buyers may care about creation method for brand safety or compliance reasons ## Webhook Configuration (Task-Specific) @@ -135,16 +136,21 @@ For generative formats, buyers submit a creative manifest (brand information + g 1. **Implementation Agnostic**: Buyer submits the same request whether the publisher uses AI, human designers, or both 2. **Async by Default**: Response time ranges from seconds (AI) to days (human review) 3. **Preview-First**: Publisher returns `preview_url` for buyer approval before campaign launch -4. **Conversational Refinement**: Buyer can request changes using the `context_id` from the response +4. **Simple Approval**: Set `approved: true` to finalize or `approved: false` with updated `generation_prompt` to request changes +5. **Conversational Refinement**: Use `context_id` from response to continue the conversation + +**Note on Transparency:** Some buyers may care about creation method (AI vs human) for brand safety, compliance, or quality reasons. Publishers should communicate their approach during format discovery or setup. **Workflow Steps:** ``` -1. Buyer submits creative with brand_manifest + generation_prompt +1. Buyer submits creative with promoted_offerings + generation_prompt 2. Publisher responds with status: "submitted" or "working" -3. Publisher creates creative (AI/human/hybrid - buyer doesn't know) +3. Publisher creates creative (AI/human/hybrid) 4. Publisher responds with status: "completed" + preview_url -5. Buyer reviews preview and approves or requests changes +5. Buyer reviews preview: + - Approve: Re-submit with approved: true + - Refine: Re-submit with approved: false + updated generation_prompt ``` **Example Generative Formats:** @@ -319,7 +325,39 @@ Submit a creative manifest for publisher to create (AI or human): } ``` -The buyer doesn't know if this creative was generated by AI in 20 seconds or designed by a human team over 2 days. The workflow is identical either way. +**Approval (Buyer likes it):** +```json +{ + "context_id": "ctx_abc123", + "creatives": [ + { + "creative_id": "holiday_hero_display", + "approved": true + } + ] +} +``` + +**Or Request Changes (Buyer wants refinement):** +```json +{ + "context_id": "ctx_abc123", + "creatives": [ + { + "creative_id": "holiday_hero_display", + "approved": false, + "assets": { + "generation_prompt": { + "asset_type": "text", + "content": "Make the colors more vibrant and emphasize the sale pricing more prominently" + } + } + } + ] +} +``` + +The buyer may not know if this creative was generated by AI in 20 seconds or designed by a human team over 2 days. The workflow is identical either way, though publishers should communicate their approach for buyers who care about creation method. ### Example 5: Dry Run Preview From 1e18ac49383dea5e59c83d4297cdda8f5e029550 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 12 Oct 2025 16:09:36 -0400 Subject: [PATCH 11/11] feat: add typed asset schemas and improve schema validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add typed asset schemas for all asset types (image, video, audio, text, html, css, javascript, promoted_offerings, url) - Update creative-asset.json to use oneOf union of typed schemas - Add test to validate schema examples against their schemas - Fix schema examples to match current schema structure - Remove context_id from task request schemas (protocol-level concern) - Update sync_creatives docs to clarify context_id is protocol-managed - Fix brand-manifest, promoted-products, and snippet-type examples 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../task-reference/sync_creatives.md | 12 ++-- .../schemas/v1/core/assets/audio-asset.json | 34 ++++++++++ static/schemas/v1/core/assets/css-asset.json | 23 +++++++ static/schemas/v1/core/assets/html-asset.json | 23 +++++++ .../schemas/v1/core/assets/image-asset.json | 38 +++++++++++ .../v1/core/assets/javascript-asset.json | 24 +++++++ .../core/assets/promoted-offerings-asset.json | 38 +++++++++++ static/schemas/v1/core/assets/text-asset.json | 28 ++++++++ static/schemas/v1/core/assets/url-asset.json | 24 +++++++ .../schemas/v1/core/assets/video-asset.json | 44 ++++++++++++ static/schemas/v1/core/brand-manifest.json | 31 +++++---- static/schemas/v1/core/creative-asset.json | 68 +++---------------- static/schemas/v1/core/promoted-products.json | 33 ++++----- static/schemas/v1/enums/snippet-type.json | 36 ++-------- .../v1/media-buy/build-creative-request.json | 4 -- .../v1/media-buy/sync-creatives-request.json | 64 +++++++++-------- tests/schema-validation.test.js | 46 ++++++++++++- 17 files changed, 417 insertions(+), 153 deletions(-) create mode 100644 static/schemas/v1/core/assets/audio-asset.json create mode 100644 static/schemas/v1/core/assets/css-asset.json create mode 100644 static/schemas/v1/core/assets/html-asset.json create mode 100644 static/schemas/v1/core/assets/image-asset.json create mode 100644 static/schemas/v1/core/assets/javascript-asset.json create mode 100644 static/schemas/v1/core/assets/promoted-offerings-asset.json create mode 100644 static/schemas/v1/core/assets/text-asset.json create mode 100644 static/schemas/v1/core/assets/url-asset.json create mode 100644 static/schemas/v1/core/assets/video-asset.json diff --git a/docs/media-buy/task-reference/sync_creatives.md b/docs/media-buy/task-reference/sync_creatives.md index 1dfe87bfcf..58f2709441 100644 --- a/docs/media-buy/task-reference/sync_creatives.md +++ b/docs/media-buy/task-reference/sync_creatives.md @@ -141,6 +141,8 @@ For generative formats, buyers submit a creative manifest (brand information + g **Note on Transparency:** Some buyers may care about creation method (AI vs human) for brand safety, compliance, or quality reasons. Publishers should communicate their approach during format discovery or setup. +**Protocol Context**: The `context_id` is managed at the protocol level (automatic in A2A, manual in MCP) and is not part of the task request parameters. See [Context Management](../../protocols/context-management.md) for details. + **Workflow Steps:** ``` @@ -270,8 +272,8 @@ Submit a creative manifest for publisher to create (AI or human): "id": "premium_bespoke_display" }, "assets": { - "brand_manifest": { - "asset_type": "brand_manifest", + "promoted_offerings": { + "asset_type": "promoted_offerings", "url": "https://retailer.com", "colors": { "primary": "#C41E3A", @@ -291,6 +293,8 @@ Submit a creative manifest for publisher to create (AI or human): } ``` +**Note:** The `url` field in the `promoted_offerings` asset represents the advertiser's brand or product website (e.g., `https://retailer.com`), not a reference to a cached manifest. Publishers can use this URL to gather additional brand context if needed. + **Initial Response (Async):** ```json { @@ -328,7 +332,6 @@ Submit a creative manifest for publisher to create (AI or human): **Approval (Buyer likes it):** ```json { - "context_id": "ctx_abc123", "creatives": [ { "creative_id": "holiday_hero_display", @@ -341,7 +344,6 @@ Submit a creative manifest for publisher to create (AI or human): **Or Request Changes (Buyer wants refinement):** ```json { - "context_id": "ctx_abc123", "creatives": [ { "creative_id": "holiday_hero_display", @@ -357,6 +359,8 @@ Submit a creative manifest for publisher to create (AI or human): } ``` +_Note: Conversational context is maintained automatically by the protocol layer - no explicit `context_id` parameter is needed in requests._ + The buyer may not know if this creative was generated by AI in 20 seconds or designed by a human team over 2 days. The workflow is identical either way, though publishers should communicate their approach for buyers who care about creation method. ### Example 5: Dry Run Preview diff --git a/static/schemas/v1/core/assets/audio-asset.json b/static/schemas/v1/core/assets/audio-asset.json new file mode 100644 index 0000000000..f96442632b --- /dev/null +++ b/static/schemas/v1/core/assets/audio-asset.json @@ -0,0 +1,34 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/audio-asset.json", + "title": "Audio Asset", + "description": "Audio asset with URL and specifications", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "audio" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL to the audio asset" + }, + "duration_ms": { + "type": "integer", + "description": "Audio duration in milliseconds", + "minimum": 0 + }, + "format": { + "type": "string", + "description": "Audio file format (mp3, wav, aac, etc.)" + }, + "bitrate_kbps": { + "type": "integer", + "description": "Audio bitrate in kilobits per second", + "minimum": 1 + } + }, + "required": ["asset_type", "url"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/assets/css-asset.json b/static/schemas/v1/core/assets/css-asset.json new file mode 100644 index 0000000000..fe9558be09 --- /dev/null +++ b/static/schemas/v1/core/assets/css-asset.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/css-asset.json", + "title": "CSS Asset", + "description": "CSS stylesheet asset", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "css" + }, + "content": { + "type": "string", + "description": "CSS content" + }, + "media": { + "type": "string", + "description": "CSS media query context (e.g., 'screen', 'print')" + } + }, + "required": ["asset_type", "content"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/assets/html-asset.json b/static/schemas/v1/core/assets/html-asset.json new file mode 100644 index 0000000000..1ee7059f87 --- /dev/null +++ b/static/schemas/v1/core/assets/html-asset.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/html-asset.json", + "title": "HTML Asset", + "description": "HTML content asset", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "html" + }, + "content": { + "type": "string", + "description": "HTML content" + }, + "version": { + "type": "string", + "description": "HTML version (e.g., 'HTML5')" + } + }, + "required": ["asset_type", "content"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/assets/image-asset.json b/static/schemas/v1/core/assets/image-asset.json new file mode 100644 index 0000000000..2477c5c133 --- /dev/null +++ b/static/schemas/v1/core/assets/image-asset.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/image-asset.json", + "title": "Image Asset", + "description": "Image asset with URL and dimensions", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "image" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL to the image asset" + }, + "width": { + "type": "integer", + "description": "Image width in pixels", + "minimum": 1 + }, + "height": { + "type": "integer", + "description": "Image height in pixels", + "minimum": 1 + }, + "format": { + "type": "string", + "description": "Image file format (jpg, png, gif, webp, etc.)" + }, + "alt_text": { + "type": "string", + "description": "Alternative text for accessibility" + } + }, + "required": ["asset_type", "url"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/assets/javascript-asset.json b/static/schemas/v1/core/assets/javascript-asset.json new file mode 100644 index 0000000000..dfb9c12ae5 --- /dev/null +++ b/static/schemas/v1/core/assets/javascript-asset.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/javascript-asset.json", + "title": "JavaScript Asset", + "description": "JavaScript code asset", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "javascript" + }, + "content": { + "type": "string", + "description": "JavaScript content" + }, + "module_type": { + "type": "string", + "enum": ["esm", "commonjs", "script"], + "description": "JavaScript module type" + } + }, + "required": ["asset_type", "content"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/assets/promoted-offerings-asset.json b/static/schemas/v1/core/assets/promoted-offerings-asset.json new file mode 100644 index 0000000000..d760873793 --- /dev/null +++ b/static/schemas/v1/core/assets/promoted-offerings-asset.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/promoted-offerings-asset.json", + "title": "Promoted Offerings Asset", + "description": "Reference to promoted offerings specification", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "promoted_offerings" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL of the advertiser's brand or offering (e.g., https://retailer.com)" + }, + "colors": { + "type": "object", + "description": "Brand colors", + "properties": { + "primary": {"type": "string"}, + "secondary": {"type": "string"}, + "accent": {"type": "string"} + } + }, + "fonts": { + "type": "array", + "description": "Brand fonts", + "items": {"type": "string"} + }, + "tone": { + "type": "string", + "description": "Brand tone/voice" + } + }, + "required": ["asset_type"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/assets/text-asset.json b/static/schemas/v1/core/assets/text-asset.json new file mode 100644 index 0000000000..b519ee4b4f --- /dev/null +++ b/static/schemas/v1/core/assets/text-asset.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/text-asset.json", + "title": "Text Asset", + "description": "Text content asset", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "text" + }, + "content": { + "type": "string", + "description": "Text content" + }, + "max_length": { + "type": "integer", + "description": "Maximum character length constraint", + "minimum": 1 + }, + "language": { + "type": "string", + "description": "Language code (e.g., 'en', 'es', 'fr')" + } + }, + "required": ["asset_type", "content"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/assets/url-asset.json b/static/schemas/v1/core/assets/url-asset.json new file mode 100644 index 0000000000..83a812df78 --- /dev/null +++ b/static/schemas/v1/core/assets/url-asset.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/url-asset.json", + "title": "URL Asset", + "description": "URL reference asset", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "url" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL reference" + }, + "description": { + "type": "string", + "description": "Description of what this URL points to" + } + }, + "required": ["asset_type", "url"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/assets/video-asset.json b/static/schemas/v1/core/assets/video-asset.json new file mode 100644 index 0000000000..6c24766e22 --- /dev/null +++ b/static/schemas/v1/core/assets/video-asset.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/core/assets/video-asset.json", + "title": "Video Asset", + "description": "Video asset with URL and specifications", + "type": "object", + "properties": { + "asset_type": { + "type": "string", + "const": "video" + }, + "url": { + "type": "string", + "format": "uri", + "description": "URL to the video asset" + }, + "width": { + "type": "integer", + "description": "Video width in pixels", + "minimum": 1 + }, + "height": { + "type": "integer", + "description": "Video height in pixels", + "minimum": 1 + }, + "duration_ms": { + "type": "integer", + "description": "Video duration in milliseconds", + "minimum": 0 + }, + "format": { + "type": "string", + "description": "Video file format (mp4, webm, mov, etc.)" + }, + "bitrate_kbps": { + "type": "integer", + "description": "Video bitrate in kilobits per second", + "minimum": 1 + } + }, + "required": ["asset_type", "url"], + "additionalProperties": false +} diff --git a/static/schemas/v1/core/brand-manifest.json b/static/schemas/v1/core/brand-manifest.json index 65a8ce3b87..b325506bce 100644 --- a/static/schemas/v1/core/brand-manifest.json +++ b/static/schemas/v1/core/brand-manifest.json @@ -284,23 +284,29 @@ "additionalProperties": false, "examples": [ { - "url": "https://bobsfunburgers.com", - "name": "Bob's Fun Burgers", - "description": "Example with both URL and name" + "description": "Example with both URL and name", + "data": { + "url": "https://bobsfunburgers.com", + "name": "Bob's Fun Burgers" + } }, { - "name": "Great Value", "description": "Example: white-label brand without dedicated URL", - "colors": { - "primary": "#0071CE", - "secondary": "#FFC220" - }, - "tone": "affordable and trustworthy" + "data": { + "name": "Great Value", + "colors": { + "primary": "#0071CE", + "secondary": "#FFC220" + }, + "tone": "affordable and trustworthy" + } }, { - "url": "https://acmecorp.com", - "name": "ACME Corporation", - "logos": [ + "description": "Full brand manifest with all fields", + "data": { + "url": "https://acmecorp.com", + "name": "ACME Corporation", + "logos": [ { "url": "https://cdn.acmecorp.com/logo-square-dark.png", "tags": ["dark", "square"], @@ -366,6 +372,7 @@ ], "industry": "technology", "target_audience": "business decision-makers aged 35-55" + } } ] } diff --git a/static/schemas/v1/core/creative-asset.json b/static/schemas/v1/core/creative-asset.json index 1459230247..5d997b77d9 100644 --- a/static/schemas/v1/core/creative-asset.json +++ b/static/schemas/v1/core/creative-asset.json @@ -22,63 +22,17 @@ "description": "Assets required by the format, keyed by asset_role", "patternProperties": { "^[a-zA-Z0-9_-]+$": { - "type": "object", - "description": "Asset definition", - "properties": { - "asset_type": { - "type": "string", - "enum": ["image", "video", "audio", "text", "html", "css", "javascript", "promoted_offerings", "url"], - "description": "Type of this asset" - }, - "url": { - "type": "string", - "format": "uri", - "description": "URL for image/video/audio assets or promoted_offerings URL reference" - }, - "content": { - "type": "string", - "description": "Content for text/html/css/javascript assets" - }, - "width": { - "type": "integer", - "description": "Width in pixels (for images/video)", - "minimum": 1 - }, - "height": { - "type": "integer", - "description": "Height in pixels (for images/video)", - "minimum": 1 - }, - "duration_ms": { - "type": "integer", - "description": "Duration in milliseconds (for video/audio)", - "minimum": 0 - }, - "format": { - "type": "string", - "description": "File format (jpg, png, mp4, etc.)" - }, - "colors": { - "type": "object", - "description": "Brand colors (for promoted_offerings asset type)", - "properties": { - "primary": {"type": "string"}, - "secondary": {"type": "string"}, - "accent": {"type": "string"} - } - }, - "fonts": { - "type": "array", - "description": "Brand fonts (for promoted_offerings asset type)", - "items": {"type": "string"} - }, - "tone": { - "type": "string", - "description": "Brand tone/voice (for promoted_offerings asset type)" - } - }, - "required": ["asset_type"], - "additionalProperties": true + "oneOf": [ + {"$ref": "/schemas/v1/core/assets/image-asset.json"}, + {"$ref": "/schemas/v1/core/assets/video-asset.json"}, + {"$ref": "/schemas/v1/core/assets/audio-asset.json"}, + {"$ref": "/schemas/v1/core/assets/text-asset.json"}, + {"$ref": "/schemas/v1/core/assets/html-asset.json"}, + {"$ref": "/schemas/v1/core/assets/css-asset.json"}, + {"$ref": "/schemas/v1/core/assets/javascript-asset.json"}, + {"$ref": "/schemas/v1/core/assets/promoted-offerings-asset.json"}, + {"$ref": "/schemas/v1/core/assets/url-asset.json"} + ] } }, "additionalProperties": false diff --git a/static/schemas/v1/core/promoted-products.json b/static/schemas/v1/core/promoted-products.json index 48ab44e0c9..faf50397d0 100644 --- a/static/schemas/v1/core/promoted-products.json +++ b/static/schemas/v1/core/promoted-products.json @@ -31,28 +31,29 @@ "additionalProperties": false, "examples": [ { - "manifest_skus": ["SKU-12345", "SKU-67890"], - "description": "Direct SKU selection for specific products from brand manifest" + "description": "Direct SKU selection for specific products from brand manifest", + "data": { + "manifest_skus": ["SKU-12345", "SKU-67890"] + } }, { - "manifest_tags": ["organic", "sauces"], - "manifest_category": "food/condiments", - "description": "UNION selection: products tagged 'organic' OR 'sauces' OR in 'food/condiments' category from brand manifest" + "description": "UNION selection: products tagged 'organic' OR 'sauces' OR in 'food/condiments' category from brand manifest", + "data": { + "manifest_tags": ["organic", "sauces"], + "manifest_category": "food/condiments" + } }, { - "manifest_query": "all Kraft Heinz pasta sauces under $5", - "description": "Natural language product selection from brand manifest" + "description": "Natural language product selection from brand manifest", + "data": { + "manifest_query": "all Kraft Heinz pasta sauces under $5" + } }, { - "manifest_tags": ["holiday"], - "additional_assets": [ - { - "asset_id": "holiday_hero", - "asset_type": "image", - "url": "https://cdn.brand.com/holiday-hero.jpg" - } - ], - "description": "Select products from manifest and add campaign-specific assets" + "description": "Select products by tags", + "data": { + "manifest_tags": ["holiday"] + } } ] } diff --git a/static/schemas/v1/enums/snippet-type.json b/static/schemas/v1/enums/snippet-type.json index 1444762b42..70b7559589 100644 --- a/static/schemas/v1/enums/snippet-type.json +++ b/static/schemas/v1/enums/snippet-type.json @@ -21,36 +21,12 @@ "daast_url": "DAAST (Digital Audio Ad Serving Template) URL for audio advertisements" }, "examples": [ - { - "type": "vast_xml", - "description": "Inline VAST XML", - "snippet": "Sample Ad..." - }, - { - "type": "vast_url", - "description": "VAST endpoint URL", - "snippet": "https://ads.example.com/vast?campaign=12345&placement=video" - }, - { - "type": "html", - "description": "HTML display ad", - "snippet": "
\"Ad\"/
" - }, - { - "type": "javascript", - "description": "JavaScript ad tag", - "snippet": "" - }, - { - "type": "iframe", - "description": "iFrame ad tag", - "snippet": "" - }, - { - "type": "daast_url", - "description": "DAAST audio ad URL", - "snippet": "https://audio-ads.example.com/daast?campaign=audio123" - } + "vast_xml", + "vast_url", + "html", + "javascript", + "iframe", + "daast_url" ], "usage": { "vast_xml": { diff --git a/static/schemas/v1/media-buy/build-creative-request.json b/static/schemas/v1/media-buy/build-creative-request.json index 942fe1715d..40053170cb 100644 --- a/static/schemas/v1/media-buy/build-creative-request.json +++ b/static/schemas/v1/media-buy/build-creative-request.json @@ -23,10 +23,6 @@ "$ref": "/schemas/v1/core/format-id.json", "description": "Format ID to generate. The format definition specifies whether output is manifest-based or code-based." }, - "context_id": { - "type": "string", - "description": "Optional context ID for iterative creative refinement" - }, "assets": { "type": "array", "description": "Available assets to use in creative generation - this is where promoted_offerings should be included as an asset", diff --git a/static/schemas/v1/media-buy/sync-creatives-request.json b/static/schemas/v1/media-buy/sync-creatives-request.json index 0be8334307..5db92c3290 100644 --- a/static/schemas/v1/media-buy/sync-creatives-request.json +++ b/static/schemas/v1/media-buy/sync-creatives-request.json @@ -11,10 +11,6 @@ "pattern": "^\\d+\\.\\d+\\.\\d+$", "default": "1.6.0" }, - "context_id": { - "type": "string", - "description": "Context ID from a previous sync_creatives response. Used for conversational refinement of generative creatives - allows approving or requesting changes to previously generated creatives." - }, "creatives": { "type": "array", "description": "Array of creative assets to sync (create or update)", @@ -67,15 +63,25 @@ "additionalProperties": false, "examples": [ { - "description": "Full sync with new and updated creatives", + "description": "Full sync with hosted video creative", "data": { "creatives": [ { "creative_id": "hero_video_30s", "name": "Brand Hero Video 30s", - "format": "video_30s_vast", - "snippet": "https://vast.example.com/video/123", - "snippet_type": "vast_url", + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "video_standard_30s" + }, + "assets": { + "video": { + "asset_type": "video", + "url": "https://cdn.example.com/hero-video.mp4", + "width": 1920, + "height": 1080, + "duration_ms": 30000 + } + }, "tags": ["q1_2024", "video"] } ], @@ -85,29 +91,33 @@ } }, { - "description": "Patch update - only update click URLs", - "data": { - "creatives": [ - { - "creative_id": "hero_video_30s", - "click_url": "https://example.com/new-landing" - } - ], - "patch": true - } - }, - { - "description": "Dry run to preview changes", + "description": "Generative creative with approval", "data": { "creatives": [ { - "creative_id": "new_banner", - "name": "Test Banner", - "format": "display_300x250", - "media_url": "https://cdn.example.com/banner.jpg" + "creative_id": "holiday_hero", + "name": "Holiday Campaign Hero", + "format_id": { + "agent_url": "https://publisher.com/.well-known/adcp/sales", + "id": "premium_bespoke_display" + }, + "assets": { + "promoted_offerings": { + "asset_type": "promoted_offerings", + "url": "https://retailer.com", + "colors": { + "primary": "#C41E3A", + "secondary": "#165B33" + } + }, + "generation_prompt": { + "asset_type": "text", + "content": "Create a warm, festive holiday campaign featuring winter products" + } + }, + "tags": ["holiday", "q4_2024"] } - ], - "dry_run": true + ] } } ] diff --git a/tests/schema-validation.test.js b/tests/schema-validation.test.js index cc209a37a4..adcc607958 100644 --- a/tests/schema-validation.test.js +++ b/tests/schema-validation.test.js @@ -279,15 +279,15 @@ async function runTests() { 'error.json': ['code', 'message'], 'budget.json': ['total', 'currency'] }; - + for (const [schemaPath, schema] of coreSchemas) { const filename = path.basename(schemaPath); const expectedRequired = requiredFieldChecks[filename]; - + if (expectedRequired) { const actualRequired = schema.required || []; const missing = expectedRequired.filter(field => !actualRequired.includes(field)); - + if (missing.length > 0) { return `${filename}: Missing required fields: ${missing.join(', ')}`; } @@ -296,6 +296,46 @@ async function runTests() { return true; }); + // Test 7: Validate schema examples against their schemas + await test('Schema examples validate against their own schemas', async () => { + const schemasWithExamples = schemas.filter(([_, schema]) => schema.examples && schema.examples.length > 0); + + for (const [schemaPath, schema] of schemasWithExamples) { + const filename = path.basename(schemaPath); + + // Compile the schema + const testAjv = new Ajv({ + allErrors: true, + verbose: true, + strict: false, + loadSchema: loadExternalSchema + }); + addFormats(testAjv); + + let validate; + try { + validate = await testAjv.compileAsync(schema); + } catch (error) { + return `${filename}: Failed to compile schema for example validation: ${error.message}`; + } + + // Validate each example + for (let i = 0; i < schema.examples.length; i++) { + const example = schema.examples[i]; + const exampleData = example.data || example; + + const valid = validate(exampleData); + if (!valid) { + const errors = validate.errors.map(err => + `${err.instancePath} ${err.message}` + ).join('; '); + return `${filename}: Example ${i + 1} ${example.description ? `"${example.description}" ` : ''}failed validation: ${errors}`; + } + } + } + return true; + }); + // Print results log('\n=========================================='); log(`Tests completed: ${totalTests}`);