diff --git a/CLAUDE.md b/CLAUDE.md index c3867f68c..0b071483f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -239,6 +239,51 @@ For major version changes: - Run tests with `npm test` before committing - Ensure new features have corresponding tests +### Format Field Naming Convention + +**CRITICAL**: Always use consistent naming for format-related fields to avoid developer confusion. + +**Established Convention**: +- **`"formats"`** = Array of format objects (with full details like name, type, requirements, assets_required, etc.) +- **`"format_ids"`** = Array of format ID strings (references to format objects) +- **`"format_types"`** = Array of high-level type strings (video, display, audio, native, etc.) + +**Examples**: +```json +// ✅ CORRECT - list_creative_formats response (format objects) +{ + "formats": [ + { + "format_id": "video_standard_30s", + "name": "Standard Video - 30 seconds", + "type": "video", + "requirements": {...} + } + ] +} + +// ✅ CORRECT - Product response (format ID strings) +{ + "product_id": "ctv_premium", + "format_ids": ["video_standard_30s", "video_standard_15s"] +} + +// ✅ CORRECT - get_products filter (high-level types) +{ + "filters": { + "format_types": ["video", "display"] + } +} +``` + +**When adding new fields**: +- Use `format_ids` when referencing existing formats by ID +- Use `formats` only when returning full format objects +- Use `format_types` for broad categorical filtering +- Never use `formats` for arrays of strings - always use `format_ids` + +**Schema Validation**: All schemas must follow this convention. Tests will fail if format fields don't match the expected naming pattern. + ## Common Tasks ### Before Making Changes diff --git a/docs/media-buy/media-products.md b/docs/media-buy/media-products.md index f4aae7c1d..3a58ec66f 100644 --- a/docs/media-buy/media-products.md +++ b/docs/media-buy/media-products.md @@ -86,7 +86,7 @@ A server can offer a general catalog, but it can also return: "product_id": "connected_tv_prime", "name": "Connected TV - Prime Time", "description": "Premium CTV inventory 8PM-11PM", - "formats": [{"format_id": "video_standard", "name": "Standard Video"}], + "format_ids": ["video_standard"], "delivery_type": "guaranteed", "is_fixed_price": true, "cpm": 45.00 @@ -99,7 +99,7 @@ A server can offer a general catalog, but it can also return: "product_id": "custom_abc123", "name": "Custom - Gaming Enthusiasts", "description": "Custom audience package for gaming campaign", - "formats": [{"format_id": "display_300x250", "name": "Medium Rectangle"}], + "format_ids": ["display_300x250"], "delivery_type": "non_guaranteed", "is_fixed_price": false, "price_guidance": { @@ -118,10 +118,10 @@ A server can offer a general catalog, but it can also return: "product_id": "albertsons_pet_category_offsite", "name": "Pet Category Shoppers - Offsite Display & Video", "description": "Target Albertsons shoppers who have purchased pet products in the last 90 days. Reach them across premium display and video inventory.", - "formats": [ - {"format_id": "display_300x250", "name": "Medium Rectangle"}, - {"format_id": "display_728x90", "name": "Leaderboard"}, - {"format_id": "video_15s_vast", "name": "15-second VAST"} + "format_ids": [ + "display_300x250", + "display_728x90", + "video_15s_vast" ], "delivery_type": "guaranteed", "is_fixed_price": true, diff --git a/docs/media-buy/tasks/create_media_buy.md b/docs/media-buy/tasks/create_media_buy.md index 64909af5e..3dfbd90a7 100644 --- a/docs/media-buy/tasks/create_media_buy.md +++ b/docs/media-buy/tasks/create_media_buy.md @@ -33,7 +33,7 @@ Create a media buy from selected packages. This task handles the complete workfl |-----------|------|----------|-------------| | `buyer_ref` | string | Yes | Buyer's reference identifier for this package | | `products` | string[] | Yes | Array of product IDs to include in this package | -| `formats` | string[] | Yes | Array of format IDs that will be used for this package - must be supported by all products | +| `format_ids` | string[] | Yes | Array of format IDs that will be used for this package - must be supported by all products | | `budget` | Budget | No | Budget configuration for this package (overrides media buy level budget if specified) | | `targeting_overlay` | TargetingOverlay | No | Additional targeting criteria for this package (see Targeting Overlay Object below) | @@ -121,7 +121,7 @@ The AdCP payload is identical across protocols. Only the request/response wrappe { "buyer_ref": "nike_ctv_sports_package", "products": ["ctv_sports_premium", "ctv_prime_time"], - "formats": ["video_standard_30s", "video_standard_15s"], + "format_ids": ["video_standard_30s", "video_standard_15s"], "budget": { "total": 60000, "currency": "USD", @@ -136,7 +136,7 @@ The AdCP payload is identical across protocols. Only the request/response wrappe { "buyer_ref": "nike_audio_drive_package", "products": ["audio_drive_time"], - "formats": ["audio_standard_30s"], + "format_ids": ["audio_standard_30s"], "budget": { "total": 40000, "currency": "USD", @@ -257,7 +257,7 @@ await a2a.send({ { "buyer_ref": "nike_ctv_sports_package", "products": ["ctv_sports_premium", "ctv_prime_time"], - "formats": ["video_standard_30s", "video_standard_15s"], + "format_ids": ["video_standard_30s", "video_standard_15s"], "budget": { "total": 60000, "currency": "USD", @@ -272,7 +272,7 @@ await a2a.send({ { "buyer_ref": "nike_audio_drive_package", "products": ["audio_drive_time"], - "formats": ["audio_standard_30s"], + "format_ids": ["audio_standard_30s"], "budget": { "total": 40000, "currency": "USD", @@ -570,7 +570,7 @@ data: {"status": {"state": "completed"}, "artifacts": [...]} { "buyer_ref": "purina_ctv_package", "products": ["ctv_prime_time", "ctv_late_night"], - "formats": ["video_standard_30s"], + "format_ids": ["video_standard_30s"], "budget": { "total": 30000, "currency": "USD", @@ -590,7 +590,7 @@ data: {"status": {"state": "completed"}, "artifacts": [...]} { "buyer_ref": "purina_audio_package", "products": ["audio_drive_time"], - "formats": ["audio_standard_30s"], + "format_ids": ["audio_standard_30s"], "budget": { "total": 20000, "currency": "USD" @@ -621,7 +621,7 @@ data: {"status": {"state": "completed"}, "artifacts": [...]} { "buyer_ref": "purina_albertsons_conquest", "products": ["albertsons_competitive_conquest", "albertsons_onsite_display"], - "formats": ["display_300x250", "display_728x90"], + "format_ids": ["display_300x250", "display_728x90"], "budget": { "total": 75000, "currency": "USD", diff --git a/docs/media-buy/tasks/get_products.md b/docs/media-buy/tasks/get_products.md index 919ce7803..90c58648e 100644 --- a/docs/media-buy/tasks/get_products.md +++ b/docs/media-buy/tasks/get_products.md @@ -25,7 +25,6 @@ Discover available advertising products based on campaign requirements, using na | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `delivery_type` | string | No | Filter by delivery type: `"guaranteed"` or `"non_guaranteed"` | -| `formats` | string[] | No | Filter by specific formats (e.g., `["video"]`) | | `is_fixed_price` | boolean | No | Filter for fixed price vs auction products | | `format_types` | string[] | No | Filter by format types (e.g., `["video", "display"]`) | | `format_ids` | string[] | No | Filter by specific format IDs (e.g., `["video_standard_30s"]`) | @@ -49,7 +48,7 @@ The message is returned differently in each protocol: "product_id": "string", "name": "string", "description": "string", - "formats": [ + "format_ids": [ "format_id_string" ], "delivery_type": "string", @@ -77,7 +76,7 @@ The message is returned differently in each protocol: - **product_id**: Unique identifier for the product - **name**: Human-readable product name - **description**: Detailed description of the product and its inventory -- **formats**: Array of supported creative format IDs (strings) - use `list_creative_formats` to get full format details +- **format_ids**: Array of supported creative format IDs (strings) - use `list_creative_formats` to get full format details - **delivery_type**: Either `"guaranteed"` or `"non_guaranteed"` - **is_fixed_price**: Whether this product has fixed pricing (true) or uses auction (false) - **cpm**: Cost per thousand impressions in USD @@ -119,7 +118,7 @@ The AdCP payload is identical across protocols. Only the request/response wrappe "product_id": "ctv_sports_premium", "name": "CTV Sports Premium", "description": "Premium CTV inventory on sports content", - "formats": ["video_16x9_30s"], + "format_ids": ["video_16x9_30s"], "delivery_type": "guaranteed", "is_fixed_price": true, "cpm": 45.00, @@ -191,7 +190,7 @@ A2A returns results as artifacts with text and data parts: "product_id": "ctv_sports_premium", "name": "CTV Sports Premium", "description": "Premium CTV inventory on sports content", - "formats": ["video_16x9_30s"], + "format_ids": ["video_16x9_30s"], "delivery_type": "guaranteed", "is_fixed_price": true, "cpm": 45.00, @@ -239,7 +238,7 @@ A2A returns results as artifacts with text and data parts: "promoted_offering": "Peloton Digital Membership - unlimited access to live and on-demand fitness classes, promoting New Year special pricing", "filters": { "delivery_type": "guaranteed", - "formats": ["video"], + "format_types": ["video"], "is_fixed_price": true, "standard_formats_only": true } @@ -264,7 +263,7 @@ A2A returns results as artifacts with text and data parts: "product_id": "open_exchange_video", "name": "Open Exchange - Video", "description": "Programmatic video inventory across all publishers", - "formats": ["video_standard"], + "format_ids": ["video_standard"], "delivery_type": "non_guaranteed", "is_fixed_price": false, "cpm": 12.00, @@ -287,7 +286,7 @@ A2A returns results as artifacts with text and data parts: "product_id": "connected_tv_prime", "name": "Connected TV - Prime Time", "description": "Premium CTV inventory 8PM-11PM", - "formats": ["video_standard"], + "format_ids": ["video_standard"], "delivery_type": "guaranteed", "is_fixed_price": true, "cpm": 45.00, @@ -309,7 +308,7 @@ A2A returns results as artifacts with text and data parts: "product_id": "albertsons_pet_category_syndicated", "name": "Pet Category Shoppers - Syndicated", "description": "Target Albertsons shoppers who have purchased pet products in the last 90 days across offsite display and video inventory.", - "formats": [ + "format_ids": [ "display_300x250", "video_15s_vast" ], @@ -335,7 +334,7 @@ A2A returns results as artifacts with text and data parts: "product_id": "albertsons_custom_competitive_conquest", "name": "Custom: Competitive Dog Food Buyers", "description": "Custom audience of Albertsons shoppers who buy competitive dog food brands. Higher precision targeting for conquest campaigns.", - "formats": [ + "format_ids": [ "display_300x250", "display_728x90" ], diff --git a/static/schemas/README.md b/static/schemas/README.md index 15eab1f8c..a64d16f68 100644 --- a/static/schemas/README.md +++ b/static/schemas/README.md @@ -94,7 +94,7 @@ const product = { "product_id": "ctv_sports_premium", "name": "CTV Sports Premium", "description": "Premium CTV inventory on sports content", - "formats": [{"format_id": "video_16x9_30s", "name": "30-second video"}], + "format_ids": ["video_16x9_30s"], "delivery_type": "guaranteed", "is_fixed_price": true }; @@ -120,7 +120,7 @@ product = { "product_id": "ctv_sports_premium", "name": "CTV Sports Premium", "description": "Premium CTV inventory on sports content", - "formats": [{"format_id": "video_16x9_30s", "name": "30-second video"}], + "format_ids": ["video_16x9_30s"], "delivery_type": "guaranteed", "is_fixed_price": True } diff --git a/static/schemas/v1/core/product.json b/static/schemas/v1/core/product.json index 89e04209b..04e0b3ca7 100644 --- a/static/schemas/v1/core/product.json +++ b/static/schemas/v1/core/product.json @@ -17,7 +17,7 @@ "type": "string", "description": "Detailed description of the product and its inventory" }, - "formats": { + "format_ids": { "type": "array", "description": "Array of supported creative format IDs - use list_creative_formats to get full format details", "items": { @@ -66,7 +66,7 @@ "product_id", "name", "description", - "formats", + "format_ids", "delivery_type", "is_fixed_price" ], 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 d362abdb6..f7cce3a24 100644 --- a/static/schemas/v1/media-buy/create-media-buy-request.json +++ b/static/schemas/v1/media-buy/create-media-buy-request.json @@ -32,7 +32,7 @@ "type": "string" } }, - "formats": { + "format_ids": { "type": "array", "description": "Array of format IDs that will be used for this package - must be supported by all products", "items": { @@ -48,7 +48,7 @@ } }, "anyOf": [ - {"required": ["buyer_ref", "products", "formats"]}, + {"required": ["buyer_ref", "products", "format_ids"]}, {"required": ["buyer_ref", "products", "format_selection"]} ], "additionalProperties": false diff --git a/static/schemas/v1/media-buy/get-products-request.json b/static/schemas/v1/media-buy/get-products-request.json index 538e9c9c1..3d33875a9 100644 --- a/static/schemas/v1/media-buy/get-products-request.json +++ b/static/schemas/v1/media-buy/get-products-request.json @@ -26,13 +26,6 @@ "delivery_type": { "$ref": "/schemas/v1/enums/delivery-type.json" }, - "formats": { - "type": "array", - "description": "Filter by specific formats", - "items": { - "type": "string" - } - }, "is_fixed_price": { "type": "boolean", "description": "Filter for fixed price vs auction products" diff --git a/static/schemas/validation-example.js b/static/schemas/validation-example.js index 36bc0a8a8..3687e5d25 100644 --- a/static/schemas/validation-example.js +++ b/static/schemas/validation-example.js @@ -37,7 +37,7 @@ const exampleProduct = { "product_id": "ctv_sports_premium", "name": "CTV Sports Premium", "description": "Premium CTV inventory on sports content", - "formats": [{"format_id": "video_16x9_30s", "name": "30-second video"}], + "format_ids": ["video_16x9_30s"], "delivery_type": "guaranteed", "is_fixed_price": true }; diff --git a/tests/example-validation.test.js b/tests/example-validation.test.js index 605a49d4c..b5f0e9607 100644 --- a/tests/example-validation.test.js +++ b/tests/example-validation.test.js @@ -121,7 +121,7 @@ const exampleData = { "product_id": "ctv_sports_premium", "name": "CTV Sports Premium", "description": "Premium CTV inventory on sports content", - "formats": [{"format_id": "video_16x9_30s", "name": "30-second video"}], + "format_ids": ["video_16x9_30s"], "delivery_type": "guaranteed", "is_fixed_price": true, "cpm": 45.00, @@ -201,7 +201,7 @@ const exampleData = { "product_id": "ctv_sports_premium", "name": "CTV Sports Premium", "description": "Premium CTV inventory on sports content", - "formats": [{"format_id": "video_16x9_30s", "name": "30-second video"}], + "format_ids": ["video_16x9_30s"], "delivery_type": "guaranteed", "is_fixed_price": true, "cpm": 45.00, diff --git a/tests/schema-validation.test.js b/tests/schema-validation.test.js index 616007c66..53707951e 100644 --- a/tests/schema-validation.test.js +++ b/tests/schema-validation.test.js @@ -272,7 +272,7 @@ async function runTests() { await test('Core schemas have appropriate required fields', () => { const coreSchemas = schemas.filter(([path]) => path.includes('/core/')); const requiredFieldChecks = { - 'product.json': ['product_id', 'name', 'description', 'formats', 'delivery_type', 'is_fixed_price'], + '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'],