diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1d61b79..2c08fc8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,6 +35,15 @@ jobs:
- name: Run mypy type checker
run: uv run mypy src/
+ - name: Check AdCP schemas are up to date
+ run: |
+ uv run python scripts/generate_schemas.py
+ if ! git diff --exit-code src/creative_agent/schemas_generated/; then
+ echo "❌ Generated schemas are out of sync!"
+ echo "Run: python scripts/generate_schemas.py"
+ exit 1
+ fi
+
- name: Run smoke tests
run: uv run pytest tests/smoke/ -v --no-cov -m smoke
diff --git a/.gitignore b/.gitignore
index 1dac950..57b2d1e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,7 @@ htmlcov/
.cache
nosetests.xml
coverage.xml
+coverage.json
*.cover
*.py.cover
.hypothesis/
@@ -210,3 +211,6 @@ __marimo__/
/test_*.py
/test_*.json
/setup_bucket_policy.py
+
+# Schema generation temporary files
+temp_resolved_schemas/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e25f017..707853f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -35,13 +35,16 @@ repos:
hooks:
- id: ruff
args: [--fix]
+ exclude: ^(scripts/|src/creative_agent/schemas_generated/)
- id: ruff-format
+ exclude: ^src/creative_agent/schemas_generated/
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.18.2
hooks:
- id: mypy
files: ^src/
+ exclude: ^src/creative_agent/schemas_generated/
additional_dependencies:
- pydantic>=2.0.0
- types-pillow>=10.0.0
diff --git a/coverage.json b/coverage.json
deleted file mode 100644
index 8b07cce..0000000
--- a/coverage.json
+++ /dev/null
@@ -1 +0,0 @@
-{"meta": {"format": 3, "version": "7.10.7", "timestamp": "2025-10-13T01:57:55.283677", "branch_coverage": false, "show_contexts": false}, "files": {"src/creative_agent/__init__.py": {"executed_lines": [1, 3], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"": {"executed_lines": [1, 3], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"": {"executed_lines": [1, 3], "summary": {"covered_lines": 1, "num_statements": 1, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "src/creative_agent/api_server.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 53, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 53, "excluded_lines": 0}, "missing_lines": [3, 4, 5, 7, 8, 9, 11, 13, 16, 25, 28, 29, 30, 31, 34, 35, 37, 47, 48, 50, 53, 54, 56, 59, 60, 62, 63, 64, 65, 66, 69, 70, 73, 74, 75, 78, 81, 82, 83, 84, 85, 87, 95, 96, 97, 98, 100, 110, 112, 121, 122, 124, 125], "excluded_lines": [], "functions": {"root": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [37], "excluded_lines": []}, "health": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [50], "excluded_lines": []}, "list_formats": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [56], "excluded_lines": []}, "get_format": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0}, "missing_lines": [62, 63, 64, 65, 66], "excluded_lines": []}, "preview_creative": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 17, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 17, "excluded_lines": 0}, "missing_lines": [73, 74, 75, 78, 81, 82, 83, 84, 85, 87, 95, 96, 97, 98, 100, 110, 112], "excluded_lines": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 28, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 28, "excluded_lines": 0}, "missing_lines": [3, 4, 5, 7, 8, 9, 11, 13, 16, 25, 28, 29, 30, 31, 34, 35, 47, 48, 53, 54, 59, 60, 69, 70, 121, 122, 124, 125], "excluded_lines": []}}, "classes": {"PreviewRequest": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 53, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 53, "excluded_lines": 0}, "missing_lines": [3, 4, 5, 7, 8, 9, 11, 13, 16, 25, 28, 29, 30, 31, 34, 35, 37, 47, 48, 50, 53, 54, 56, 59, 60, 62, 63, 64, 65, 66, 69, 70, 73, 74, 75, 78, 81, 82, 83, 84, 85, 87, 95, 96, 97, 98, 100, 110, 112, 121, 122, 124, 125], "excluded_lines": []}}}, "src/creative_agent/data/standard_formats.py": {"executed_lines": [1, 3, 6, 7, 8, 11, 25, 232, 495, 710, 853, 969, 1051, 1162, 1173, 1175, 1176, 1177, 1181], "summary": {"covered_lines": 18, "num_statements": 32, "percent_covered": 56.25, "percent_covered_display": "56", "missing_lines": 14, "excluded_lines": 0}, "missing_lines": [1178, 1189, 1191, 1192, 1194, 1195, 1197, 1198, 1200, 1201, 1202, 1204, 1206, 1212], "excluded_lines": [], "functions": {"get_format_by_id": {"executed_lines": [1175, 1176, 1177], "summary": {"covered_lines": 3, "num_statements": 4, "percent_covered": 75.0, "percent_covered_display": "75", "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [1178], "excluded_lines": []}, "filter_formats": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 13, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 13, "excluded_lines": 0}, "missing_lines": [1189, 1191, 1192, 1194, 1195, 1197, 1198, 1200, 1201, 1202, 1204, 1206, 1212], "excluded_lines": []}, "": {"executed_lines": [1, 3, 6, 7, 8, 11, 25, 232, 495, 710, 853, 969, 1051, 1162, 1173, 1181], "summary": {"covered_lines": 15, "num_statements": 15, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"": {"executed_lines": [1, 3, 6, 7, 8, 11, 25, 232, 495, 710, 853, 969, 1051, 1162, 1173, 1175, 1176, 1177, 1181], "summary": {"covered_lines": 18, "num_statements": 32, "percent_covered": 56.25, "percent_covered_display": "56", "missing_lines": 14, "excluded_lines": 0}, "missing_lines": [1178, 1189, 1191, 1192, 1194, 1195, 1197, 1198, 1200, 1201, 1202, 1204, 1206, 1212], "excluded_lines": []}}}, "src/creative_agent/http_server.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 10, 12], "excluded_lines": [], "functions": {"": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 10, 12], "excluded_lines": []}}, "classes": {"": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 5, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 5, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 10, 12], "excluded_lines": []}}}, "src/creative_agent/schemas/__init__.py": {"executed_lines": [1, 3, 15, 22, 30], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"": {"executed_lines": [1, 3, 15, 22, 30], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"": {"executed_lines": [1, 3, 15, 22, 30], "summary": {"covered_lines": 4, "num_statements": 4, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "src/creative_agent/schemas/assets.py": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 15, 16, 17, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 34, 35, 37, 38, 39, 40, 41, 42, 43, 44, 45, 48, 49, 51, 52, 53, 54, 55, 58, 59, 61, 62, 63, 64, 67, 68, 70, 71, 72, 75, 76, 78, 79, 80, 81, 82, 83, 86, 87, 89, 90, 91, 92, 95, 96, 98, 99, 100, 101, 102, 103, 104, 105, 108, 109, 111, 112, 116], "summary": {"covered_lines": 70, "num_statements": 70, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 15, 16, 17, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 34, 35, 37, 38, 39, 40, 41, 42, 43, 44, 45, 48, 49, 51, 52, 53, 54, 55, 58, 59, 61, 62, 63, 64, 67, 68, 70, 71, 72, 75, 76, 78, 79, 80, 81, 82, 83, 86, 87, 89, 90, 91, 92, 95, 96, 98, 99, 100, 101, 102, 103, 104, 105, 108, 109, 111, 112, 116], "summary": {"covered_lines": 70, "num_statements": 70, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"ImageAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "VideoAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "AudioAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "VastTagAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "TextAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "UrlAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "HtmlAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "JavaScriptAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "WebhookAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PromotedOfferingsAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 15, 16, 17, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 34, 35, 37, 38, 39, 40, 41, 42, 43, 44, 45, 48, 49, 51, 52, 53, 54, 55, 58, 59, 61, 62, 63, 64, 67, 68, 70, 71, 72, 75, 76, 78, 79, 80, 81, 82, 83, 86, 87, 89, 90, 91, 92, 95, 96, 98, 99, 100, 101, 102, 103, 104, 105, 108, 109, 111, 112, 116], "summary": {"covered_lines": 70, "num_statements": 70, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "src/creative_agent/schemas/brand_card.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 17, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 17, "excluded_lines": 0}, "missing_lines": [3, 6, 9, 10, 11, 12, 13, 14, 15, 18, 21, 22, 23, 24, 25, 26, 27], "excluded_lines": [], "functions": {"": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 17, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 17, "excluded_lines": 0}, "missing_lines": [3, 6, 9, 10, 11, 12, 13, 14, 15, 18, 21, 22, 23, 24, 25, 26, 27], "excluded_lines": []}}, "classes": {"BrandAsset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "BrandCard": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 17, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 17, "excluded_lines": 0}, "missing_lines": [3, 6, 9, 10, 11, 12, 13, 14, 15, 18, 21, 22, 23, 24, 25, 26, 27], "excluded_lines": []}}}, "src/creative_agent/schemas/build.py": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 17, 18, 20, 21, 24, 25, 27, 28, 31, 32, 34, 35, 36, 37, 38, 39, 40, 41, 43, 46, 47, 49, 50, 51, 52, 55, 56, 58, 59, 60, 61, 62, 63], "summary": {"covered_lines": 35, "num_statements": 35, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 17, 18, 20, 21, 24, 25, 27, 28, 31, 32, 34, 35, 36, 37, 38, 39, 40, 41, 43, 46, 47, 49, 50, 51, 52, 55, 56, 58, 59, 60, 61, 62, 63], "summary": {"covered_lines": 35, "num_statements": 35, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"AssetReference": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewContext": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewOptions": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "BuildCreativeRequest": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "CreativeOutput": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "BuildCreativeResponse": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 17, 18, 20, 21, 24, 25, 27, 28, 31, 32, 34, 35, 36, 37, 38, 39, 40, 41, 43, 46, 47, 49, 50, 51, 52, 55, 56, 58, 59, 60, 61, 62, 63], "summary": {"covered_lines": 35, "num_statements": 35, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "src/creative_agent/schemas/format.py": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 25, 26, 28, 29, 30, 31, 32, 33, 36, 37, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 51, 52, 53, 54, 55, 57, 58, 61, 62, 64, 65, 66, 67, 68, 69, 72, 73, 75, 76, 77, 80, 81, 83, 84, 85, 86, 87], "summary": {"covered_lines": 56, "num_statements": 56, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 25, 26, 28, 29, 30, 31, 32, 33, 36, 37, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 51, 52, 53, 54, 55, 57, 58, 61, 62, 64, 65, 66, 67, 68, 69, 72, 73, 75, 76, 77, 80, 81, 83, 84, 85, 86, 87], "summary": {"covered_lines": 56, "num_statements": 56, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"AssetRequirement": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "FormatRequirements": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "CreativeFormat": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "ListCreativeFormatsRequest": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "CreativeAgentInfo": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "ListCreativeFormatsResponse": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [1, 3, 5, 8, 9, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 25, 26, 28, 29, 30, 31, 32, 33, 36, 37, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 51, 52, 53, 54, 55, 57, 58, 61, 62, 64, 65, 66, 67, 68, 69, 72, 73, 75, 76, 77, 80, 81, 83, 84, 85, 86, 87], "summary": {"covered_lines": 56, "num_statements": 56, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "src/creative_agent/schemas/library.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 36, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 36, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27, 28, 29, 30, 33, 36, 37, 38, 41, 44, 45, 46, 47, 48, 49, 50, 51, 54, 57, 58, 59], "excluded_lines": [], "functions": {"": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 36, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 36, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27, 28, 29, 30, 33, 36, 37, 38, 41, 44, 45, 46, 47, 48, 49, 50, 51, 54, 57, 58, 59], "excluded_lines": []}}, "classes": {"AssetMetadata": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "Asset": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "SearchFilters": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "ManageCreativeLibraryRequest": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "ManageCreativeLibraryResponse": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 36, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 36, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27, 28, 29, 30, 33, 36, 37, 38, 41, 44, 45, 46, 47, 48, 49, 50, 51, 54, 57, 58, 59], "excluded_lines": []}}}, "src/creative_agent/schemas/manifest.py": {"executed_lines": [1, 3, 5, 7, 10, 11, 13, 14, 15, 16, 19, 20, 22, 23, 24, 27, 28, 30, 31, 32, 33, 34, 35, 36, 39, 40, 42, 43, 44, 45, 46, 49, 50, 52, 53, 54, 55, 58, 59, 61, 62, 63, 64, 67, 68, 70, 71, 72, 73], "summary": {"covered_lines": 41, "num_statements": 41, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": [], "functions": {"": {"executed_lines": [1, 3, 5, 7, 10, 11, 13, 14, 15, 16, 19, 20, 22, 23, 24, 27, 28, 30, 31, 32, 33, 34, 35, 36, 39, 40, 42, 43, 44, 45, 46, 49, 50, 52, 53, 54, 55, 58, 59, 61, 62, 63, 64, 67, 68, 70, 71, 72, 73], "summary": {"covered_lines": 41, "num_statements": 41, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}, "classes": {"CreativeManifest": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewInput": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewCreativeRequest": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewHints": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewEmbedding": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewVariant": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewCreativeResponse": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [1, 3, 5, 7, 10, 11, 13, 14, 15, 16, 19, 20, 22, 23, 24, 27, 28, 30, 31, 32, 33, 34, 35, 36, 39, 40, 42, 43, 44, 45, 46, 49, 50, 52, 53, 54, 55, 58, 59, 61, 62, 63, 64, 67, 68, 70, 71, 72, 73], "summary": {"covered_lines": 41, "num_statements": 41, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}}}, "src/creative_agent/schemas/preview.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 26, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 26, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27, 28, 31, 34, 35, 36, 37, 38, 41, 44, 45], "excluded_lines": [], "functions": {"": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 26, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 26, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27, 28, 31, 34, 35, 36, 37, 38, 41, 44, 45], "excluded_lines": []}}, "classes": {"PreviewVariant": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewCreativeRequest": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewResponse": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "PreviewCreativeResponse": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 0, "percent_covered": 100.0, "percent_covered_display": "100", "missing_lines": 0, "excluded_lines": 0}, "missing_lines": [], "excluded_lines": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 26, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 26, "excluded_lines": 0}, "missing_lines": [3, 5, 8, 11, 12, 13, 14, 15, 16, 19, 22, 23, 24, 25, 26, 27, 28, 31, 34, 35, 36, 37, 38, 41, 44, 45], "excluded_lines": []}}}, "src/creative_agent/server.py": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 11, 16, 17, 18, 20, 23, 24, 61, 62, 172, 222, 223, 588], "summary": {"covered_lines": 19, "num_statements": 217, "percent_covered": 8.755760368663594, "percent_covered_display": "9", "missing_lines": 198, "excluded_lines": 0}, "missing_lines": [43, 44, 52, 56, 57, 58, 85, 87, 90, 93, 94, 95, 98, 109, 110, 111, 117, 118, 126, 127, 130, 131, 138, 140, 142, 145, 146, 149, 156, 159, 161, 167, 168, 169, 180, 183, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 199, 202, 203, 204, 205, 208, 214, 254, 256, 257, 266, 268, 269, 272, 273, 274, 277, 278, 279, 282, 283, 284, 287, 288, 294, 307, 308, 309, 315, 316, 317, 318, 319, 323, 326, 327, 329, 333, 335, 340, 341, 342, 343, 345, 347, 348, 349, 350, 351, 354, 355, 356, 357, 360, 361, 362, 363, 364, 365, 366, 367, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 382, 387, 389, 390, 415, 438, 441, 444, 446, 448, 449, 450, 451, 454, 455, 458, 459, 460, 461, 462, 464, 465, 466, 469, 470, 471, 474, 475, 477, 479, 481, 487, 492, 493, 495, 496, 497, 498, 499, 501, 502, 504, 506, 508, 509, 510, 511, 514, 516, 517, 518, 521, 524, 527, 528, 529, 530, 531, 533, 534, 537, 540, 543, 544, 545, 546, 548, 551, 554, 574, 576, 577, 581, 582, 590, 591, 592, 595], "excluded_lines": [], "functions": {"list_creative_formats": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 6, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 6, "excluded_lines": 0}, "missing_lines": [43, 44, 52, 56, 57, 58], "excluded_lines": []}, "preview_creative": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 28, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 28, "excluded_lines": 0}, "missing_lines": [85, 87, 90, 93, 94, 95, 98, 109, 110, 111, 117, 118, 126, 127, 130, 131, 138, 140, 142, 145, 146, 149, 156, 159, 161, 167, 168, 169], "excluded_lines": []}, "_generate_preview_variant": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 22, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 22, "excluded_lines": 0}, "missing_lines": [180, 183, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 199, 202, 203, 204, 205, 208, 214], "excluded_lines": []}, "build_creative": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 138, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 138, "excluded_lines": 0}, "missing_lines": [254, 256, 257, 266, 268, 269, 272, 273, 274, 277, 278, 279, 282, 283, 284, 287, 288, 294, 307, 308, 309, 315, 316, 317, 318, 319, 323, 326, 327, 329, 333, 335, 340, 341, 342, 343, 345, 347, 348, 349, 350, 351, 354, 355, 356, 357, 360, 361, 362, 363, 364, 365, 366, 367, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 382, 387, 389, 390, 415, 438, 441, 444, 446, 448, 449, 450, 451, 454, 455, 458, 459, 460, 461, 462, 464, 465, 466, 469, 470, 471, 474, 475, 477, 479, 481, 487, 492, 493, 495, 496, 497, 498, 499, 501, 502, 504, 506, 508, 509, 510, 511, 514, 516, 517, 518, 521, 524, 527, 528, 529, 530, 531, 533, 534, 537, 540, 543, 544, 545, 546, 548, 551, 554, 574, 576, 577, 581, 582], "excluded_lines": []}, "": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 11, 16, 17, 18, 20, 23, 24, 61, 62, 172, 222, 223, 588], "summary": {"covered_lines": 19, "num_statements": 23, "percent_covered": 82.6086956521739, "percent_covered_display": "83", "missing_lines": 4, "excluded_lines": 0}, "missing_lines": [590, 591, 592, 595], "excluded_lines": []}}, "classes": {"": {"executed_lines": [1, 3, 4, 5, 6, 7, 9, 11, 16, 17, 18, 20, 23, 24, 61, 62, 172, 222, 223, 588], "summary": {"covered_lines": 19, "num_statements": 217, "percent_covered": 8.755760368663594, "percent_covered_display": "9", "missing_lines": 198, "excluded_lines": 0}, "missing_lines": [43, 44, 52, 56, 57, 58, 85, 87, 90, 93, 94, 95, 98, 109, 110, 111, 117, 118, 126, 127, 130, 131, 138, 140, 142, 145, 146, 149, 156, 159, 161, 167, 168, 169, 180, 183, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 199, 202, 203, 204, 205, 208, 214, 254, 256, 257, 266, 268, 269, 272, 273, 274, 277, 278, 279, 282, 283, 284, 287, 288, 294, 307, 308, 309, 315, 316, 317, 318, 319, 323, 326, 327, 329, 333, 335, 340, 341, 342, 343, 345, 347, 348, 349, 350, 351, 354, 355, 356, 357, 360, 361, 362, 363, 364, 365, 366, 367, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 382, 387, 389, 390, 415, 438, 441, 444, 446, 448, 449, 450, 451, 454, 455, 458, 459, 460, 461, 462, 464, 465, 466, 469, 470, 471, 474, 475, 477, 479, 481, 487, 492, 493, 495, 496, 497, 498, 499, 501, 502, 504, 506, 508, 509, 510, 511, 514, 516, 517, 518, 521, 524, 527, 528, 529, 530, 531, 533, 534, 537, 540, 543, 544, 545, 546, 548, 551, 554, 574, 576, 577, 581, 582, 590, 591, 592, 595], "excluded_lines": []}}}, "src/creative_agent/storage.py": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 44, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 44, "excluded_lines": 0}, "missing_lines": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 19, 29, 40, 43, 46, 56, 59, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 88, 89, 90, 91, 94, 135, 136, 138, 140, 146, 147, 149, 151, 156], "excluded_lines": [], "functions": {"get_s3_client": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 1, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 1, "excluded_lines": 0}, "missing_lines": [19], "excluded_lines": []}, "upload_preview_html": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 4, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 4, "excluded_lines": 0}, "missing_lines": [40, 43, 46, 56], "excluded_lines": []}, "generate_preview_html": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 27, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 27, "excluded_lines": 0}, "missing_lines": [71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 88, 89, 90, 91, 94, 135, 136, 138, 140, 146, 147, 149, 151, 156], "excluded_lines": []}, "": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 12, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 12, "excluded_lines": 0}, "missing_lines": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 29, 59], "excluded_lines": []}}, "classes": {"": {"executed_lines": [], "summary": {"covered_lines": 0, "num_statements": 44, "percent_covered": 0.0, "percent_covered_display": "0", "missing_lines": 44, "excluded_lines": 0}, "missing_lines": [3, 4, 6, 7, 10, 11, 12, 13, 14, 17, 19, 29, 40, 43, 46, 56, 59, 71, 72, 73, 74, 75, 76, 77, 80, 81, 82, 83, 84, 87, 88, 89, 90, 91, 94, 135, 136, 138, 140, 146, 147, 149, 151, 156], "excluded_lines": []}}}}, "totals": {"covered_lines": 244, "num_statements": 637, "percent_covered": 38.30455259026687, "percent_covered_display": "38", "missing_lines": 393, "excluded_lines": 0}}
diff --git a/mypy.ini b/mypy.ini
index aa80750..eea3207 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -40,6 +40,7 @@ exclude = (?x)(
| ^\.git/
| ^__pycache__/
| \.pyc$
+ | ^src/creative_agent/schemas_generated/
)
# Per-module settings - keep tests less strict
diff --git a/pyproject.toml b/pyproject.toml
index 245d1a4..d02dfa7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -37,6 +37,9 @@ dev = [
"pytest-cov>=6.2.1",
"pytest-mock>=3.14.1",
"ruff>=0.8.0",
+ # Schema generation
+ "datamodel-code-generator>=0.26.0",
+ "jsonref>=1.1.0",
# Type stubs
"boto3-stubs[s3]>=1.35.0",
"types-pillow>=10.0.0",
@@ -52,6 +55,7 @@ exclude = [
".venv",
"build",
"dist",
+ "src/creative_agent/schemas_generated",
]
[tool.ruff.lint]
@@ -110,9 +114,29 @@ ignore = [
"ANN", # type annotations (less strict in tests)
"PLR2004", # magic values (ok in tests)
]
+"scripts/*" = [
+ "PTH123", # open() is acceptable in scripts
+ "S603", # subprocess with list is safe
+ "ANN201", # type annotations less critical in scripts
+]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
+
+[tool.coverage.run]
+branch = true
+source = ["src/creative_agent"]
+omit = [
+ "tests/*",
+ "*/__pycache__/*",
+ "*/.venv/*",
+ "src/creative_agent/schemas_generated/*",
+]
+
+[tool.coverage.report]
+precision = 2
+show_missing = true
+skip_covered = false
diff --git a/pytest.ini b/pytest.ini
index 952cf79..5c1b5da 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -39,6 +39,7 @@ omit =
tests/*
*/__pycache__/*
*/.venv/*
+ src/creative_agent/schemas_generated/*
[coverage:report]
precision = 2
diff --git a/scripts/convert_to_adcp_dicts.py b/scripts/convert_to_adcp_dicts.py
new file mode 100644
index 0000000..7530d20
--- /dev/null
+++ b/scripts/convert_to_adcp_dicts.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+"""Convert AssetRequirement and FormatRequirements to AdCP-compliant dicts."""
+
+import re
+from pathlib import Path
+
+
+def convert_asset_requirement(match: re.Match) -> str:
+ """Convert AssetRequirement(...) to AdCP dict format."""
+ content = match.group(1)
+
+ # Extract fields
+ asset_role = re.search(r'asset_role="([^"]+)"', content)
+ asset_type = re.search(r'asset_type="([^"]+)"', content)
+ required = re.search(r"required=(\w+)", content)
+
+ # Build dict
+ result = "{\n"
+
+ # asset_id = asset_role
+ if asset_role:
+ result += f' "asset_id": "{asset_role.group(1)}",\n'
+ result += f' "asset_type": "{asset_type.group(1) if asset_type else "text"}",\n'
+ result += f' "asset_role": "{asset_role.group(1)}",\n'
+
+ if required:
+ result += f' "required": {required.group(1)},\n'
+
+ # Collect other fields into requirements dict
+ req_dict = {}
+ for line in content.split("\n"):
+ line = line.strip()
+ if not line:
+ continue
+ # Skip already handled fields
+ if any(x in line for x in ["asset_role=", "asset_type=", "required="]):
+ continue
+ # Extract field name and value
+ if "=" in line:
+ field_match = re.match(r"(\w+)=(.+?)(?:,|$)", line)
+ if field_match:
+ field_name = field_match.group(1)
+ field_value = field_match.group(2).strip(",")
+ req_dict[field_name] = field_value
+
+ if req_dict:
+ result += ' "requirements": {\n'
+ for name, value in req_dict.items():
+ result += f' "{name}": {value},\n'
+ result += " },\n"
+
+ result += " }"
+ return result
+
+
+def convert_format_requirements(match: re.Match) -> str:
+ """Convert FormatRequirements(...) to dict."""
+ content = match.group(1)
+
+ result = "{\n"
+ for line in content.split("\n"):
+ line = line.strip()
+ if not line or line == ")":
+ continue
+ # Extract field name and value
+ field_match = re.match(r"(\w+)=(.+?)(?:,|$)", line)
+ if field_match:
+ field_name = field_match.group(1)
+ field_value = field_match.group(2).strip(",")
+ result += f' "{field_name}": {field_value},\n'
+ result += " }"
+ return result
+
+
+def main():
+ file_path = Path(__file__).parent.parent / "src/creative_agent/data/standard_formats.py"
+ content = file_path.read_text()
+
+ # Remove imports
+ content = re.sub(
+ r"from \.\.schemas import AssetRequirement, CreativeFormat, FormatRequirements",
+ "from ..schemas import CreativeFormat",
+ content,
+ )
+
+ # Convert AssetRequirement
+ content = re.sub(
+ r"AssetRequirement\(((?:[^()]|\([^)]*\))*)\)",
+ convert_asset_requirement,
+ content,
+ flags=re.MULTILINE | re.DOTALL,
+ )
+
+ # Convert FormatRequirements
+ content = re.sub(
+ r"FormatRequirements\(((?:[^()]|\([^)]*\))*)\)",
+ convert_format_requirements,
+ content,
+ flags=re.MULTILINE | re.DOTALL,
+ )
+
+ # Fix dimensions field (move to requirements)
+ # This is complex, so we'll do it manually after
+
+ file_path.write_text(content)
+ print(f"✓ Converted {file_path}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/generate_schemas.py b/scripts/generate_schemas.py
new file mode 100755
index 0000000..39b3f66
--- /dev/null
+++ b/scripts/generate_schemas.py
@@ -0,0 +1,291 @@
+#!/usr/bin/env python3
+"""
+Generate Pydantic models from AdCP JSON schemas.
+
+This script uses datamodel-code-generator to auto-generate Pydantic models
+from the official AdCP JSON schemas cached in tests/schemas/v1/.
+
+The script handles $ref resolution by creating a custom loader that maps
+the official $ref paths to our flattened file structure.
+
+Usage:
+ python scripts/generate_schemas.py [--output OUTPUT_FILE]
+
+The generated models should match the official AdCP spec exactly.
+"""
+
+import argparse
+import json
+import subprocess
+import sys
+from pathlib import Path
+
+import httpx
+
+
+def load_schema_with_resolver(schema_path: Path, schema_dir: Path) -> dict:
+ """
+ Load a schema and create a custom loader for $ref resolution.
+
+ This function creates a loader that maps AdCP $ref paths like
+ "/schemas/v1/enums/pacing.json" to our flattened file structure
+ "_schemas_v1_enums_pacing_json.json".
+ """
+
+ def ref_to_filename(ref: str) -> str:
+ """Convert $ref path to our flattened filename format."""
+ # /schemas/v1/enums/pacing.json -> _schemas_v1_enums_pacing_json.json
+ return ref.replace("/", "_").replace(".", "_") + ".json"
+
+ def load_ref(ref: str) -> dict:
+ """Load a schema from a $ref path."""
+ filename = ref_to_filename(ref)
+ ref_path = schema_dir / filename
+
+ if not ref_path.exists():
+ raise FileNotFoundError(f"Referenced schema not found: {ref} (looked for {ref_path})")
+
+ with open(ref_path) as f:
+ return json.load(f)
+
+ return load_ref
+
+
+def download_missing_schema(ref: str, schema_dir: Path) -> bool:
+ """
+ Download a missing schema from AdCP website.
+
+ Returns True if download successful, False otherwise.
+ """
+ # Validate ref starts with /schemas/v1/
+ if not ref.startswith("/schemas/v1/"):
+ print(f" ⚠️ Invalid schema ref (must start with /schemas/v1/): {ref}", file=sys.stderr)
+ return False
+
+ # Prevent path traversal
+ if ".." in ref or ref.count("//") > 0:
+ print(f" ⚠️ Invalid schema ref (contains path traversal): {ref}", file=sys.stderr)
+ return False
+
+ base_url = "https://adcontextprotocol.org"
+ schema_url = f"{base_url}{ref}"
+ ref_filename = ref.replace("/", "_").replace(".", "_") + ".json"
+ ref_path = schema_dir / ref_filename
+
+ try:
+ print(f" 📥 Downloading missing schema: {ref}")
+ response = httpx.get(schema_url, timeout=10.0)
+ response.raise_for_status()
+
+ schema = response.json()
+
+ # Save to cache
+ with open(ref_path, "w") as f:
+ json.dump(schema, f, indent=2)
+
+ print(f" ✅ Downloaded: {ref_filename}")
+ return True
+
+ except Exception as e:
+ print(f" ❌ Failed to download {ref}: {e}", file=sys.stderr)
+ return False
+
+
+def resolve_refs_in_schema(schema: dict, schema_dir: Path, visited: set | None = None) -> dict:
+ """
+ Recursively resolve all $ref references in a schema.
+
+ Returns a new schema dict with all references inlined.
+ Downloads missing schemas from AdCP website automatically.
+ """
+ if visited is None:
+ visited = set()
+
+ # Handle $ref
+ if "$ref" in schema:
+ ref = schema["$ref"]
+
+ # Avoid circular references
+ if ref in visited:
+ return {"description": f"Circular reference to {ref}"}
+
+ visited.add(ref)
+
+ # Load referenced schema
+ ref_filename = ref.replace("/", "_").replace(".", "_") + ".json"
+ ref_path = schema_dir / ref_filename
+
+ if not ref_path.exists():
+ # Try downloading missing schema
+ if not download_missing_schema(ref, schema_dir):
+ print(f"⚠️ Warning: Cannot resolve $ref: {ref}", file=sys.stderr)
+ return schema
+
+ with open(ref_path) as f:
+ ref_schema = json.load(f)
+
+ # Recursively resolve references in the loaded schema
+ resolved = resolve_refs_in_schema(ref_schema, schema_dir, visited)
+
+ # Merge any properties from original schema (e.g., description)
+ for key, value in schema.items():
+ if key != "$ref" and key not in resolved:
+ resolved[key] = value
+
+ return resolved
+
+ # Recursively process nested schemas
+ result = {}
+ for key, value in schema.items():
+ if isinstance(value, dict):
+ result[key] = resolve_refs_in_schema(value, schema_dir, visited)
+ elif isinstance(value, list):
+ result[key] = [
+ resolve_refs_in_schema(item, schema_dir, visited) if isinstance(item, dict) else item for item in value
+ ]
+ else:
+ result[key] = value
+
+ return result
+
+
+def generate_schemas_from_json(schema_dir: Path, output_file: Path):
+ """
+ Generate Pydantic models from JSON schemas with proper $ref resolution.
+ """
+ print(f"📂 Processing schemas from: {schema_dir}")
+
+ # Create temporary directory for resolved schemas
+ temp_dir = Path("temp_resolved_schemas")
+ temp_dir.mkdir(exist_ok=True)
+
+ try:
+ # Process each JSON schema file
+ schema_files = list(schema_dir.glob("*.json"))
+ print(f"📝 Found {len(schema_files)} schema files")
+
+ # Skip these non-schema files
+ skip_files = {"index.json", "SCHEMAS_INFO.md"}
+
+ for schema_file in schema_files:
+ if schema_file.name in skip_files:
+ continue
+
+ print(f" Processing: {schema_file.name}")
+
+ # Load and resolve all $refs
+ with open(schema_file) as f:
+ schema = json.load(f)
+
+ resolved_schema = resolve_refs_in_schema(schema, schema_dir)
+
+ # Write resolved schema to temp directory
+ temp_file = temp_dir / schema_file.name
+ with open(temp_file, "w") as f:
+ json.dump(resolved_schema, f, indent=2)
+
+ print(f"✅ Resolved all $refs, generated {len(list(temp_dir.glob('*.json')))} schemas")
+
+ # Now run datamodel-codegen on resolved schemas
+ print("\n🔧 Generating Pydantic models...")
+
+ cmd = [
+ "datamodel-codegen",
+ "--input",
+ str(temp_dir),
+ "--output",
+ str(output_file),
+ "--input-file-type",
+ "jsonschema",
+ "--output-model-type",
+ "pydantic_v2.BaseModel",
+ "--use-annotated",
+ "--field-constraints",
+ "--use-standard-collections",
+ "--collapse-root-models",
+ "--use-double-quotes",
+ "--snake-case-field",
+ "--target-python-version",
+ "3.12",
+ "--disable-timestamp",
+ ]
+
+ result = subprocess.run(cmd, capture_output=True, text=True, check=False)
+
+ if result.returncode != 0:
+ print("❌ Generation failed:", file=sys.stderr)
+ print(result.stderr, file=sys.stderr)
+ sys.exit(1)
+
+ print(f"✅ Generated Pydantic models: {output_file}")
+
+ # Add header comment to __init__.py
+ init_file = output_file / "__init__.py"
+ if not init_file.exists():
+ init_file.touch()
+
+ header = '''"""
+Auto-generated Pydantic models from AdCP JSON schemas.
+
+⚠️ DO NOT EDIT FILES IN THIS DIRECTORY MANUALLY!
+
+Generated from: tests/schemas/v1/
+Generator: scripts/generate_schemas.py
+Tool: datamodel-code-generator + custom $ref resolution
+
+To regenerate:
+ python scripts/generate_schemas.py
+
+Source: https://adcontextprotocol.org/schemas/v1/
+AdCP Version: v2.4 (schemas v1)
+"""
+'''
+
+ with open(init_file, "w") as f:
+ f.write(header)
+
+ print("✅ Added header to __init__.py")
+
+ finally:
+ # Clean up temp directory
+ import shutil
+
+ if temp_dir.exists():
+ shutil.rmtree(temp_dir)
+ print("🧹 Cleaned up temporary files")
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Generate Pydantic models from AdCP JSON schemas")
+ parser.add_argument(
+ "--output",
+ type=Path,
+ default=Path("src/creative_agent/schemas_generated"),
+ help="Output directory for generated schemas (default: src/creative_agent/schemas_generated/)",
+ )
+ parser.add_argument(
+ "--schema-dir",
+ type=Path,
+ default=Path("tests/schemas/v1"),
+ help="Directory containing JSON schemas (default: tests/schemas/v1)",
+ )
+ args = parser.parse_args()
+
+ if not args.schema_dir.exists():
+ print(f"❌ Schema directory not found: {args.schema_dir}", file=sys.stderr)
+ sys.exit(1)
+
+ # Create output directory if needed
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+
+ generate_schemas_from_json(args.schema_dir, args.output)
+
+ print("\n📊 Next steps:")
+ print(" 1. Review generated schemas in", args.output)
+ print(" 2. Compare with manual schemas in src/creative_agent/schemas/")
+ print(" 3. Identify which models to use (generated vs manual)")
+ print(" 4. Run tests to ensure compatibility")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/creative_agent/data/standard_formats.py b/src/creative_agent/data/standard_formats.py
index a70345a..b541b8b 100644
--- a/src/creative_agent/data/standard_formats.py
+++ b/src/creative_agent/data/standard_formats.py
@@ -1,9 +1,12 @@
"""Standard AdCP creative formats."""
-from ..schemas.format import AssetRequirement, CreativeFormat, FormatRequirements
+from pydantic import AnyUrl
+
+from ..schemas import CreativeFormat
+from ..schemas_generated._schemas_v1_core_format_json import AssetsRequired, AssetType, Category, Type
# Agent configuration
-AGENT_URL = "https://creative.adcontextprotocol.org"
+AGENT_URL = AnyUrl("https://creative.adcontextprotocol.org")
AGENT_NAME = "AdCP Standard Creative Agent"
AGENT_CAPABILITIES = ["validation", "assembly", "generation", "preview"]
@@ -22,33 +25,35 @@
]
# Generative Formats - AI-powered creative generation
+# These use type='display' (what they generate) and output_format_ids
+# to specify what standard formats they can produce
GENERATIVE_FORMATS = [
CreativeFormat(
format_id="display_300x250_generative",
agent_url=AGENT_URL,
name="Medium Rectangle - AI Generated",
- type="universal",
- category="custom",
- is_standard=False,
+ type=Type.display,
+ category=Category.standard,
+ is_standard=True,
description="AI-generated 300x250 banner from brand context and prompt",
- dimensions="300x250",
+ requirements={"dimensions": "300x250"},
output_format_ids=["display_300x250_image"],
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="brand_context",
+ asset_type=AssetType.brand_manifest,
asset_role="brand_context",
- asset_type="brand_manifest",
required=True,
- description="Brand information and product offerings for AI generation",
+ requirements={"description": "Brand information and product offerings for AI generation"},
),
- AssetRequirement(
+ AssetsRequired(
asset_id="generation_prompt",
+ asset_type=AssetType.text,
asset_role="generation_prompt",
- asset_type="text",
required=True,
- description="Text prompt describing the desired creative",
+ requirements={"description": "Text prompt describing the desired creative"},
),
],
),
@@ -56,28 +61,28 @@
format_id="display_728x90_generative",
agent_url=AGENT_URL,
name="Leaderboard - AI Generated",
- type="universal",
- category="custom",
- is_standard=False,
+ type=Type.display,
+ category=Category.standard,
+ is_standard=True,
description="AI-generated 728x90 banner from brand context and prompt",
- dimensions="728x90",
+ requirements={"dimensions": "728x90"},
output_format_ids=["display_728x90_image"],
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="brand_context",
+ asset_type=AssetType.brand_manifest,
asset_role="brand_context",
- asset_type="brand_manifest",
required=True,
- description="Brand information and product offerings for AI generation",
+ requirements={"description": "Brand information and product offerings for AI generation"},
),
- AssetRequirement(
+ AssetsRequired(
asset_id="generation_prompt",
+ asset_type=AssetType.text,
asset_role="generation_prompt",
- asset_type="text",
required=True,
- description="Text prompt describing the desired creative",
+ requirements={"description": "Text prompt describing the desired creative"},
),
],
),
@@ -85,28 +90,28 @@
format_id="display_320x50_generative",
agent_url=AGENT_URL,
name="Mobile Banner - AI Generated",
- type="universal",
- category="custom",
- is_standard=False,
+ type=Type.display,
+ category=Category.standard,
+ is_standard=True,
description="AI-generated 320x50 mobile banner from brand context and prompt",
- dimensions="320x50",
+ requirements={"dimensions": "320x50"},
output_format_ids=["display_320x50_image"],
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="brand_context",
+ asset_type=AssetType.brand_manifest,
asset_role="brand_context",
- asset_type="brand_manifest",
required=True,
- description="Brand information and product offerings for AI generation",
+ requirements={"description": "Brand information and product offerings for AI generation"},
),
- AssetRequirement(
+ AssetsRequired(
asset_id="generation_prompt",
+ asset_type=AssetType.text,
asset_role="generation_prompt",
- asset_type="text",
required=True,
- description="Text prompt describing the desired creative",
+ requirements={"description": "Text prompt describing the desired creative"},
),
],
),
@@ -114,28 +119,28 @@
format_id="display_160x600_generative",
agent_url=AGENT_URL,
name="Wide Skyscraper - AI Generated",
- type="universal",
- category="custom",
- is_standard=False,
+ type=Type.display,
+ category=Category.standard,
+ is_standard=True,
description="AI-generated 160x600 wide skyscraper from brand context and prompt",
- dimensions="160x600",
+ requirements={"dimensions": "160x600"},
output_format_ids=["display_160x600_image"],
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="brand_context",
+ asset_type=AssetType.brand_manifest,
asset_role="brand_context",
- asset_type="brand_manifest",
required=True,
- description="Brand information and product offerings for AI generation",
+ requirements={"description": "Brand information and product offerings for AI generation"},
),
- AssetRequirement(
+ AssetsRequired(
asset_id="generation_prompt",
+ asset_type=AssetType.text,
asset_role="generation_prompt",
- asset_type="text",
required=True,
- description="Text prompt describing the desired creative",
+ requirements={"description": "Text prompt describing the desired creative"},
),
],
),
@@ -143,28 +148,28 @@
format_id="display_336x280_generative",
agent_url=AGENT_URL,
name="Large Rectangle - AI Generated",
- type="universal",
- category="custom",
- is_standard=False,
+ type=Type.display,
+ category=Category.standard,
+ is_standard=True,
description="AI-generated 336x280 large rectangle from brand context and prompt",
- dimensions="336x280",
+ requirements={"dimensions": "336x280"},
output_format_ids=["display_336x280_image"],
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="brand_context",
+ asset_type=AssetType.brand_manifest,
asset_role="brand_context",
- asset_type="brand_manifest",
required=True,
- description="Brand information and product offerings for AI generation",
+ requirements={"description": "Brand information and product offerings for AI generation"},
),
- AssetRequirement(
+ AssetsRequired(
asset_id="generation_prompt",
+ asset_type=AssetType.text,
asset_role="generation_prompt",
- asset_type="text",
required=True,
- description="Text prompt describing the desired creative",
+ requirements={"description": "Text prompt describing the desired creative"},
),
],
),
@@ -172,28 +177,28 @@
format_id="display_300x600_generative",
agent_url=AGENT_URL,
name="Half Page - AI Generated",
- type="universal",
- category="custom",
- is_standard=False,
+ type=Type.display,
+ category=Category.standard,
+ is_standard=True,
description="AI-generated 300x600 half page from brand context and prompt",
- dimensions="300x600",
+ requirements={"dimensions": "300x600"},
output_format_ids=["display_300x600_image"],
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="brand_context",
+ asset_type=AssetType.brand_manifest,
asset_role="brand_context",
- asset_type="brand_manifest",
required=True,
- description="Brand information and product offerings for AI generation",
+ requirements={"description": "Brand information and product offerings for AI generation"},
),
- AssetRequirement(
+ AssetsRequired(
asset_id="generation_prompt",
+ asset_type=AssetType.text,
asset_role="generation_prompt",
- asset_type="text",
required=True,
- description="Text prompt describing the desired creative",
+ requirements={"description": "Text prompt describing the desired creative"},
),
],
),
@@ -201,28 +206,28 @@
format_id="display_970x250_generative",
agent_url=AGENT_URL,
name="Billboard - AI Generated",
- type="universal",
- category="custom",
- is_standard=False,
+ type=Type.display,
+ category=Category.standard,
+ is_standard=True,
description="AI-generated 970x250 billboard from brand context and prompt",
- dimensions="970x250",
+ requirements={"dimensions": "970x250"},
output_format_ids=["display_970x250_image"],
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="brand_context",
+ asset_type=AssetType.brand_manifest,
asset_role="brand_context",
- asset_type="brand_manifest",
required=True,
- description="Brand information and product offerings for AI generation",
+ requirements={"description": "Brand information and product offerings for AI generation"},
),
- AssetRequirement(
+ AssetsRequired(
asset_id="generation_prompt",
+ asset_type=AssetType.text,
asset_role="generation_prompt",
- asset_type="text",
required=True,
- description="Text prompt describing the desired creative",
+ requirements={"description": "Text prompt describing the desired creative"},
),
],
),
@@ -234,28 +239,29 @@
format_id="video_standard_30s",
agent_url=AGENT_URL,
name="Standard Video - 30 seconds",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="30-second video ad in standard aspect ratios",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=True,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"],
- requirements=FormatRequirements(
- duration_seconds=30,
- max_file_size_mb=50,
- acceptable_formats=["mp4", "mov", "webm"],
- aspect_ratios=["16:9", "9:16", "1:1", "4:5"],
- ),
+ requirements={
+ "duration_seconds": 30,
+ "max_file_size_mb": 50,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "aspect_ratios": ["16:9", "9:16", "1:1", "4:5"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="video_file",
+ asset_type=AssetType.video,
asset_role="video_file",
- asset_type="video",
required=True,
- duration_seconds=30,
- acceptable_formats=["mp4", "mov", "webm"],
- description="30-second video file",
+ requirements={
+ "duration_seconds": 30,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "description": "30-second video file",
+ },
),
],
),
@@ -263,27 +269,29 @@
format_id="video_standard_15s",
agent_url=AGENT_URL,
name="Standard Video - 15 seconds",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="15-second video ad in standard aspect ratios",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=True,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"],
- requirements=FormatRequirements(
- duration_seconds=15,
- max_file_size_mb=25,
- acceptable_formats=["mp4", "mov", "webm"],
- aspect_ratios=["16:9", "9:16", "1:1", "4:5"],
- ),
+ requirements={
+ "duration_seconds": 15,
+ "max_file_size_mb": 25,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "aspect_ratios": ["16:9", "9:16", "1:1", "4:5"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="video_file",
+ asset_type=AssetType.video,
asset_role="video_file",
- asset_type="video",
required=True,
- duration_seconds=15,
- acceptable_formats=["mp4", "mov", "webm"],
+ requirements={
+ "duration_seconds": 15,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "description": "15-second video file",
+ },
),
],
),
@@ -291,23 +299,24 @@
format_id="video_vast_30s",
agent_url=AGENT_URL,
name="VAST Video - 30 seconds",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="30-second video ad via VAST tag",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=True,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"],
- requirements=FormatRequirements(
- duration_seconds=30,
- ),
+ requirements={
+ "duration_seconds": 30,
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="vast_tag",
+ asset_type=AssetType.text,
asset_role="vast_tag",
- asset_type="html", # VAST tags are XML/HTML
required=True,
- description="VAST 4.x compatible tag",
+ requirements={
+ "description": "VAST 4.x compatible tag",
+ },
),
],
),
@@ -315,29 +324,30 @@
format_id="video_1920x1080",
agent_url=AGENT_URL,
name="Full HD Video - 1920x1080",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="1920x1080 Full HD video (16:9)",
- dimensions="1920x1080",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=False,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"],
- requirements=FormatRequirements(
- max_file_size_mb=100,
- acceptable_formats=["mp4", "mov", "webm"],
- aspect_ratios=["16:9"],
- ),
+ requirements={
+ "dimensions": "1920x1080",
+ "max_file_size_mb": 100,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "aspect_ratios": ["16:9"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="video_file",
+ asset_type=AssetType.video,
asset_role="video_file",
- asset_type="video",
required=True,
- width=1920,
- height=1080,
- acceptable_formats=["mp4", "mov", "webm"],
- description="1920x1080 video file",
+ requirements={
+ "width": 1920,
+ "height": 1080,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "description": "1920x1080 video file",
+ },
),
],
),
@@ -345,29 +355,30 @@
format_id="video_1280x720",
agent_url=AGENT_URL,
name="HD Video - 1280x720",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="1280x720 HD video (16:9)",
- dimensions="1280x720",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=False,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"],
- requirements=FormatRequirements(
- max_file_size_mb=75,
- acceptable_formats=["mp4", "mov", "webm"],
- aspect_ratios=["16:9"],
- ),
+ requirements={
+ "dimensions": "1280x720",
+ "max_file_size_mb": 75,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "aspect_ratios": ["16:9"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="video_file",
+ asset_type=AssetType.video,
asset_role="video_file",
- asset_type="video",
required=True,
- width=1280,
- height=720,
- acceptable_formats=["mp4", "mov", "webm"],
- description="1280x720 video file",
+ requirements={
+ "width": 1280,
+ "height": 720,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "description": "1280x720 video file",
+ },
),
],
),
@@ -375,29 +386,30 @@
format_id="video_1080x1920",
agent_url=AGENT_URL,
name="Vertical Video - 1080x1920",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="1080x1920 vertical video (9:16) for mobile stories",
- dimensions="1080x1920",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=False,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"],
- requirements=FormatRequirements(
- max_file_size_mb=100,
- acceptable_formats=["mp4", "mov", "webm"],
- aspect_ratios=["9:16"],
- ),
+ requirements={
+ "dimensions": "1080x1920",
+ "max_file_size_mb": 100,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "aspect_ratios": ["9:16"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="video_file",
+ asset_type=AssetType.video,
asset_role="video_file",
- asset_type="video",
required=True,
- width=1080,
- height=1920,
- acceptable_formats=["mp4", "mov", "webm"],
- description="1080x1920 vertical video file",
+ requirements={
+ "width": 1080,
+ "height": 1920,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "description": "1080x1920 vertical video file",
+ },
),
],
),
@@ -405,29 +417,30 @@
format_id="video_1080x1080",
agent_url=AGENT_URL,
name="Square Video - 1080x1080",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="1080x1080 square video (1:1) for social feeds",
- dimensions="1080x1080",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=False,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE"],
- requirements=FormatRequirements(
- max_file_size_mb=100,
- acceptable_formats=["mp4", "mov", "webm"],
- aspect_ratios=["1:1"],
- ),
+ requirements={
+ "dimensions": "1080x1080",
+ "max_file_size_mb": 100,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "aspect_ratios": ["1:1"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="video_file",
+ asset_type=AssetType.video,
asset_role="video_file",
- asset_type="video",
required=True,
- width=1080,
- height=1080,
- acceptable_formats=["mp4", "mov", "webm"],
- description="1080x1080 square video file",
+ requirements={
+ "width": 1080,
+ "height": 1080,
+ "acceptable_formats": ["mp4", "mov", "webm"],
+ "description": "1080x1080 square video file",
+ },
),
],
),
@@ -435,28 +448,29 @@
format_id="video_ctv_preroll_30s",
agent_url=AGENT_URL,
name="CTV Pre-Roll - 30 seconds",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="30-second pre-roll ad for Connected TV and streaming platforms",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=True,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE", "PLAYER_SIZE"],
- requirements=FormatRequirements(
- duration_seconds=30,
- max_file_size_mb=75,
- acceptable_formats=["mp4", "mov"],
- aspect_ratios=["16:9"],
- ),
+ requirements={
+ "duration_seconds": 30,
+ "max_file_size_mb": 75,
+ "acceptable_formats": ["mp4", "mov"],
+ "aspect_ratios": ["16:9"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="video_file",
+ asset_type=AssetType.video,
asset_role="video_file",
- asset_type="video",
required=True,
- duration_seconds=30,
- acceptable_formats=["mp4", "mov"],
- description="30-second CTV-optimized video file (1920x1080 recommended)",
+ requirements={
+ "duration_seconds": 30,
+ "acceptable_formats": ["mp4", "mov"],
+ "description": "30-second CTV-optimized video file (1920x1080 recommended)",
+ },
),
],
),
@@ -464,28 +478,29 @@
format_id="video_ctv_midroll_30s",
agent_url=AGENT_URL,
name="CTV Mid-Roll - 30 seconds",
- type="video",
- category="standard",
+ type=Type.video,
+ category=Category.standard,
is_standard=True,
description="30-second mid-roll ad for Connected TV and streaming platforms",
- iab_specification="https://iabtechlab.com/standards/video-ad-serving-template-vast/",
accepts_3p_tags=True,
supported_macros=[*COMMON_MACROS, "VIDEO_ID", "POD_POSITION", "CONTENT_GENRE", "PLAYER_SIZE"],
- requirements=FormatRequirements(
- duration_seconds=30,
- max_file_size_mb=75,
- acceptable_formats=["mp4", "mov"],
- aspect_ratios=["16:9"],
- ),
+ requirements={
+ "duration_seconds": 30,
+ "max_file_size_mb": 75,
+ "acceptable_formats": ["mp4", "mov"],
+ "aspect_ratios": ["16:9"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="video_file",
+ asset_type=AssetType.video,
asset_role="video_file",
- asset_type="video",
required=True,
- duration_seconds=30,
- acceptable_formats=["mp4", "mov"],
- description="30-second CTV-optimized video file (1920x1080 recommended)",
+ requirements={
+ "duration_seconds": 30,
+ "acceptable_formats": ["mp4", "mov"],
+ "description": "30-second CTV-optimized video file (1920x1080 recommended)",
+ },
),
],
),
@@ -497,30 +512,36 @@
format_id="display_300x250_image",
agent_url=AGENT_URL,
name="Medium Rectangle - Image",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="300x250 static image banner",
- dimensions="300x250",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "300x250",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="banner_image",
+ asset_type=AssetType.image,
asset_role="banner_image",
- asset_type="image",
required=True,
- width=300,
- height=250,
- max_file_size_mb=0.2, # 200kb
- acceptable_formats=["jpg", "png", "gif", "webp"],
+ requirements={
+ "width": 300,
+ "height": 250,
+ "max_file_size_mb": 0.2,
+ "acceptable_formats": ["jpg", "png", "gif", "webp"],
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="click_url",
+ asset_type=AssetType.url,
asset_role="click_url",
- asset_type="url",
required=True,
- description="Clickthrough destination URL",
+ requirements={
+ "description": "Clickthrough destination URL",
+ },
),
],
),
@@ -528,28 +549,32 @@
format_id="display_728x90_image",
agent_url=AGENT_URL,
name="Leaderboard - Image",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="728x90 static image banner",
- dimensions="728x90",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "728x90",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="banner_image",
+ asset_type=AssetType.image,
asset_role="banner_image",
- asset_type="image",
required=True,
- width=728,
- height=90,
- max_file_size_mb=0.15,
- acceptable_formats=["jpg", "png", "gif", "webp"],
+ requirements={
+ "width": 728,
+ "height": 90,
+ "max_file_size_mb": 0.15,
+ "acceptable_formats": ["jpg", "png", "gif", "webp"],
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="click_url",
+ asset_type=AssetType.url,
asset_role="click_url",
- asset_type="url",
required=True,
),
],
@@ -558,28 +583,32 @@
format_id="display_320x50_image",
agent_url=AGENT_URL,
name="Mobile Banner - Image",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="320x50 mobile banner",
- dimensions="320x50",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "320x50",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="banner_image",
+ asset_type=AssetType.image,
asset_role="banner_image",
- asset_type="image",
required=True,
- width=320,
- height=50,
- max_file_size_mb=0.05,
- acceptable_formats=["jpg", "png", "gif", "webp"],
+ requirements={
+ "width": 320,
+ "height": 50,
+ "max_file_size_mb": 0.05,
+ "acceptable_formats": ["jpg", "png", "gif", "webp"],
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="click_url",
+ asset_type=AssetType.url,
asset_role="click_url",
- asset_type="url",
required=True,
),
],
@@ -588,28 +617,32 @@
format_id="display_160x600_image",
agent_url=AGENT_URL,
name="Wide Skyscraper - Image",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="160x600 wide skyscraper banner",
- dimensions="160x600",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "160x600",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="banner_image",
+ asset_type=AssetType.image,
asset_role="banner_image",
- asset_type="image",
required=True,
- width=160,
- height=600,
- max_file_size_mb=0.15,
- acceptable_formats=["jpg", "png", "gif", "webp"],
+ requirements={
+ "width": 160,
+ "height": 600,
+ "max_file_size_mb": 0.15,
+ "acceptable_formats": ["jpg", "png", "gif", "webp"],
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="click_url",
+ asset_type=AssetType.url,
asset_role="click_url",
- asset_type="url",
required=True,
),
],
@@ -618,28 +651,32 @@
format_id="display_336x280_image",
agent_url=AGENT_URL,
name="Large Rectangle - Image",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="336x280 large rectangle banner",
- dimensions="336x280",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "336x280",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="banner_image",
+ asset_type=AssetType.image,
asset_role="banner_image",
- asset_type="image",
required=True,
- width=336,
- height=280,
- max_file_size_mb=0.25,
- acceptable_formats=["jpg", "png", "gif", "webp"],
+ requirements={
+ "width": 336,
+ "height": 280,
+ "max_file_size_mb": 0.25,
+ "acceptable_formats": ["jpg", "png", "gif", "webp"],
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="click_url",
+ asset_type=AssetType.url,
asset_role="click_url",
- asset_type="url",
required=True,
),
],
@@ -648,28 +685,32 @@
format_id="display_300x600_image",
agent_url=AGENT_URL,
name="Half Page - Image",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="300x600 half page banner",
- dimensions="300x600",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "300x600",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="banner_image",
+ asset_type=AssetType.image,
asset_role="banner_image",
- asset_type="image",
required=True,
- width=300,
- height=600,
- max_file_size_mb=0.3,
- acceptable_formats=["jpg", "png", "gif", "webp"],
+ requirements={
+ "width": 300,
+ "height": 600,
+ "max_file_size_mb": 0.3,
+ "acceptable_formats": ["jpg", "png", "gif", "webp"],
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="click_url",
+ asset_type=AssetType.url,
asset_role="click_url",
- asset_type="url",
required=True,
),
],
@@ -678,28 +719,32 @@
format_id="display_970x250_image",
agent_url=AGENT_URL,
name="Billboard - Image",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="970x250 billboard banner",
- dimensions="970x250",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "970x250",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="banner_image",
+ asset_type=AssetType.image,
asset_role="banner_image",
- asset_type="image",
required=True,
- width=970,
- height=250,
- max_file_size_mb=0.4,
- acceptable_formats=["jpg", "png", "gif", "webp"],
+ requirements={
+ "width": 970,
+ "height": 250,
+ "max_file_size_mb": 0.4,
+ "acceptable_formats": ["jpg", "png", "gif", "webp"],
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="click_url",
+ asset_type=AssetType.url,
asset_role="click_url",
- asset_type="url",
required=True,
),
],
@@ -712,23 +757,27 @@
format_id="display_300x250_html",
agent_url=AGENT_URL,
name="Medium Rectangle - HTML5",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="300x250 HTML5 creative",
- dimensions="300x250",
accepts_3p_tags=True,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "300x250",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="html_creative",
+ asset_type=AssetType.html,
asset_role="html_creative",
- asset_type="html",
required=True,
- width=300,
- height=250,
- max_file_size_mb=0.5,
- description="HTML5 creative code",
+ requirements={
+ "width": 300,
+ "height": 250,
+ "max_file_size_mb": 0.5,
+ "description": "HTML5 creative code",
+ },
),
],
),
@@ -736,22 +785,26 @@
format_id="display_728x90_html",
agent_url=AGENT_URL,
name="Leaderboard - HTML5",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="728x90 HTML5 creative",
- dimensions="728x90",
accepts_3p_tags=True,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "728x90",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="html_creative",
+ asset_type=AssetType.html,
asset_role="html_creative",
- asset_type="html",
required=True,
- width=728,
- height=90,
- max_file_size_mb=0.5,
+ requirements={
+ "width": 728,
+ "height": 90,
+ "max_file_size_mb": 0.5,
+ },
),
],
),
@@ -759,22 +812,26 @@
format_id="display_160x600_html",
agent_url=AGENT_URL,
name="Wide Skyscraper - HTML5",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="160x600 HTML5 creative",
- dimensions="160x600",
accepts_3p_tags=True,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "160x600",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="html_creative",
+ asset_type=AssetType.html,
asset_role="html_creative",
- asset_type="html",
required=True,
- width=160,
- height=600,
- max_file_size_mb=0.5,
+ requirements={
+ "width": 160,
+ "height": 600,
+ "max_file_size_mb": 0.5,
+ },
),
],
),
@@ -782,22 +839,26 @@
format_id="display_336x280_html",
agent_url=AGENT_URL,
name="Large Rectangle - HTML5",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="336x280 HTML5 creative",
- dimensions="336x280",
accepts_3p_tags=True,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "336x280",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="html_creative",
+ asset_type=AssetType.html,
asset_role="html_creative",
- asset_type="html",
required=True,
- width=336,
- height=280,
- max_file_size_mb=0.5,
+ requirements={
+ "width": 336,
+ "height": 280,
+ "max_file_size_mb": 0.5,
+ },
),
],
),
@@ -805,22 +866,26 @@
format_id="display_300x600_html",
agent_url=AGENT_URL,
name="Half Page - HTML5",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="300x600 HTML5 creative",
- dimensions="300x600",
accepts_3p_tags=True,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "300x600",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="html_creative",
+ asset_type=AssetType.html,
asset_role="html_creative",
- asset_type="html",
required=True,
- width=300,
- height=600,
- max_file_size_mb=0.5,
+ requirements={
+ "width": 300,
+ "height": 600,
+ "max_file_size_mb": 0.5,
+ },
),
],
),
@@ -828,22 +893,26 @@
format_id="display_970x250_html",
agent_url=AGENT_URL,
name="Billboard - HTML5",
- type="display",
- category="standard",
+ type=Type.display,
+ category=Category.standard,
is_standard=True,
description="970x250 HTML5 creative",
- dimensions="970x250",
accepts_3p_tags=True,
supported_macros=COMMON_MACROS,
+ requirements={
+ "dimensions": "970x250",
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="html_creative",
+ asset_type=AssetType.html,
asset_role="html_creative",
- asset_type="html",
required=True,
- width=970,
- height=250,
- max_file_size_mb=0.5,
+ requirements={
+ "width": 970,
+ "height": 250,
+ "max_file_size_mb": 0.5,
+ },
),
],
),
@@ -855,55 +924,66 @@
format_id="native_standard",
agent_url=AGENT_URL,
name="IAB Native Standard",
- type="native",
- category="standard",
+ type=Type.native,
+ category=Category.standard,
is_standard=True,
description="Standard native ad with title, description, image, and CTA",
- iab_specification="https://iabtechlab.com/standards/openrtb-native/",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="title",
+ asset_type=AssetType.text,
asset_role="title",
- asset_type="text",
required=True,
- description="Headline text (25 chars recommended)",
+ requirements={
+ "description": "Headline text (25 chars recommended)",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="description",
+ asset_type=AssetType.text,
asset_role="description",
- asset_type="text",
required=True,
- description="Body copy (90 chars recommended)",
+ requirements={
+ "description": "Body copy (90 chars recommended)",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="main_image",
+ asset_type=AssetType.image,
asset_role="main_image",
- asset_type="image",
required=True,
- description="Primary image (1200x627 recommended)",
+ requirements={
+ "description": "Primary image (1200x627 recommended)",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="icon",
+ asset_type=AssetType.image,
asset_role="icon",
- asset_type="image",
required=False,
- description="Brand icon (square, 200x200 recommended)",
+ requirements={
+ "description": "Brand icon (square, 200x200 recommended)",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="cta_text",
+ asset_type=AssetType.text,
asset_role="cta_text",
- asset_type="text",
required=True,
- description="Call-to-action text",
+ requirements={
+ "description": "Call-to-action text",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="sponsored_by",
+ asset_type=AssetType.text,
asset_role="sponsored_by",
- asset_type="text",
required=True,
- description="Advertiser name for disclosure",
+ requirements={
+ "description": "Advertiser name for disclosure",
+ },
),
],
),
@@ -911,55 +991,66 @@
format_id="native_content",
agent_url=AGENT_URL,
name="Native Content Placement",
- type="native",
- category="standard",
+ type=Type.native,
+ category=Category.standard,
is_standard=True,
description="In-article native ad with editorial styling",
- iab_specification="https://iabtechlab.com/standards/openrtb-native/",
accepts_3p_tags=False,
supported_macros=COMMON_MACROS,
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="headline",
+ asset_type=AssetType.text,
asset_role="headline",
- asset_type="text",
required=True,
- description="Editorial-style headline (60 chars recommended)",
+ requirements={
+ "description": "Editorial-style headline (60 chars recommended)",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="body",
+ asset_type=AssetType.text,
asset_role="body",
- asset_type="text",
required=True,
- description="Article-style body copy (200 chars recommended)",
+ requirements={
+ "description": "Article-style body copy (200 chars recommended)",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="thumbnail",
+ asset_type=AssetType.image,
asset_role="thumbnail",
- asset_type="image",
required=True,
- description="Thumbnail image (square, 300x300 recommended)",
+ requirements={
+ "description": "Thumbnail image (square, 300x300 recommended)",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="author",
+ asset_type=AssetType.text,
asset_role="author",
- asset_type="text",
required=False,
- description="Author name for editorial context",
+ requirements={
+ "description": "Author name for editorial context",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="click_url",
+ asset_type=AssetType.url,
asset_role="click_url",
- asset_type="url",
required=True,
- description="Landing page URL",
+ requirements={
+ "description": "Landing page URL",
+ },
),
- AssetRequirement(
+ AssetsRequired(
asset_id="disclosure",
+ asset_type=AssetType.text,
asset_role="disclosure",
- asset_type="text",
required=True,
- description="Sponsored content disclosure text",
+ requirements={
+ "description": "Sponsored content disclosure text",
+ },
),
],
),
@@ -971,25 +1062,27 @@
format_id="audio_standard_15s",
agent_url=AGENT_URL,
name="Standard Audio - 15 seconds",
- type="audio",
- category="standard",
+ type=Type.audio,
+ category=Category.standard,
is_standard=True,
description="15-second audio ad",
accepts_3p_tags=True,
supported_macros=[*COMMON_MACROS, "CONTENT_GENRE"],
- requirements=FormatRequirements(
- duration_seconds=15,
- max_file_size_mb=0.75,
- acceptable_formats=["mp3", "aac", "m4a"],
- ),
+ requirements={
+ "duration_seconds": 15,
+ "max_file_size_mb": 0.75,
+ "acceptable_formats": ["mp3", "aac", "m4a"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="audio_file",
+ asset_type=AssetType.audio,
asset_role="audio_file",
- asset_type="audio",
required=True,
- duration_seconds=15,
- acceptable_formats=["mp3", "aac", "m4a"],
+ requirements={
+ "duration_seconds": 15,
+ "acceptable_formats": ["mp3", "aac", "m4a"],
+ },
),
],
),
@@ -997,25 +1090,27 @@
format_id="audio_standard_30s",
agent_url=AGENT_URL,
name="Standard Audio - 30 seconds",
- type="audio",
- category="standard",
+ type=Type.audio,
+ category=Category.standard,
is_standard=True,
description="30-second audio ad",
accepts_3p_tags=True,
supported_macros=[*COMMON_MACROS, "CONTENT_GENRE"],
- requirements=FormatRequirements(
- duration_seconds=30,
- max_file_size_mb=1.5,
- acceptable_formats=["mp3", "aac", "m4a"],
- ),
+ requirements={
+ "duration_seconds": 30,
+ "max_file_size_mb": 1.5,
+ "acceptable_formats": ["mp3", "aac", "m4a"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="audio_file",
+ asset_type=AssetType.audio,
asset_role="audio_file",
- asset_type="audio",
required=True,
- duration_seconds=30,
- acceptable_formats=["mp3", "aac", "m4a"],
+ requirements={
+ "duration_seconds": 30,
+ "acceptable_formats": ["mp3", "aac", "m4a"],
+ },
),
],
),
@@ -1023,25 +1118,27 @@
format_id="audio_standard_60s",
agent_url=AGENT_URL,
name="Standard Audio - 60 seconds",
- type="audio",
- category="standard",
+ type=Type.audio,
+ category=Category.standard,
is_standard=True,
description="60-second audio ad",
accepts_3p_tags=True,
supported_macros=[*COMMON_MACROS, "CONTENT_GENRE"],
- requirements=FormatRequirements(
- duration_seconds=60,
- max_file_size_mb=3,
- acceptable_formats=["mp3", "aac", "m4a"],
- ),
+ requirements={
+ "duration_seconds": 60,
+ "max_file_size_mb": 3,
+ "acceptable_formats": ["mp3", "aac", "m4a"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="audio_file",
+ asset_type=AssetType.audio,
asset_role="audio_file",
- asset_type="audio",
required=True,
- duration_seconds=60,
- acceptable_formats=["mp3", "aac", "m4a"],
+ requirements={
+ "duration_seconds": 60,
+ "acceptable_formats": ["mp3", "aac", "m4a"],
+ },
),
],
),
@@ -1053,26 +1150,28 @@
format_id="dooh_billboard_1920x1080",
agent_url=AGENT_URL,
name="Digital Billboard - 1920x1080",
- type="dooh",
- category="standard",
+ type=Type.dooh,
+ category=Category.standard,
is_standard=True,
description="Full HD digital billboard",
- dimensions="1920x1080",
accepts_3p_tags=False,
supported_macros=[*COMMON_MACROS, "SCREEN_ID", "VENUE_TYPE", "VENUE_LAT", "VENUE_LONG"],
- requirements=FormatRequirements(
- duration_seconds=10, # Typical DOOH loop duration
- max_file_size_mb=5,
- ),
+ requirements={
+ "dimensions": "1920x1080",
+ "duration_seconds": 10,
+ "max_file_size_mb": 5,
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="billboard_image",
+ asset_type=AssetType.image,
asset_role="billboard_image",
- asset_type="image",
required=True,
- width=1920,
- height=1080,
- acceptable_formats=["jpg", "png"],
+ requirements={
+ "width": 1920,
+ "height": 1080,
+ "acceptable_formats": ["jpg", "png"],
+ },
),
],
),
@@ -1080,25 +1179,27 @@
format_id="dooh_billboard_landscape",
agent_url=AGENT_URL,
name="Digital Billboard - Landscape",
- type="dooh",
- category="standard",
+ type=Type.dooh,
+ category=Category.standard,
is_standard=True,
description="Landscape-oriented digital billboard (various sizes)",
accepts_3p_tags=False,
supported_macros=[*COMMON_MACROS, "SCREEN_ID", "VENUE_TYPE", "VENUE_LAT", "VENUE_LONG"],
- requirements=FormatRequirements(
- duration_seconds=10,
- max_file_size_mb=10,
- aspect_ratios=["16:9", "21:9"],
- ),
+ requirements={
+ "duration_seconds": 10,
+ "max_file_size_mb": 10,
+ "aspect_ratios": ["16:9", "21:9"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="billboard_image",
+ asset_type=AssetType.image,
asset_role="billboard_image",
- asset_type="image",
required=True,
- acceptable_formats=["jpg", "png"],
- description="Landscape image (1920x1080 or larger)",
+ requirements={
+ "acceptable_formats": ["jpg", "png"],
+ "description": "Landscape image (1920x1080 or larger)",
+ },
),
],
),
@@ -1106,25 +1207,27 @@
format_id="dooh_billboard_portrait",
agent_url=AGENT_URL,
name="Digital Billboard - Portrait",
- type="dooh",
- category="standard",
+ type=Type.dooh,
+ category=Category.standard,
is_standard=True,
description="Portrait-oriented digital billboard (various sizes)",
accepts_3p_tags=False,
supported_macros=[*COMMON_MACROS, "SCREEN_ID", "VENUE_TYPE", "VENUE_LAT", "VENUE_LONG"],
- requirements=FormatRequirements(
- duration_seconds=10,
- max_file_size_mb=10,
- aspect_ratios=["9:16"],
- ),
+ requirements={
+ "duration_seconds": 10,
+ "max_file_size_mb": 10,
+ "aspect_ratios": ["9:16"],
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="billboard_image",
+ asset_type=AssetType.image,
asset_role="billboard_image",
- asset_type="image",
required=True,
- acceptable_formats=["jpg", "png"],
- description="Portrait image (1080x1920 or similar)",
+ requirements={
+ "acceptable_formats": ["jpg", "png"],
+ "description": "Portrait image (1080x1920 or similar)",
+ },
),
],
),
@@ -1132,27 +1235,29 @@
format_id="dooh_transit_screen",
agent_url=AGENT_URL,
name="Transit Screen",
- type="dooh",
- category="standard",
+ type=Type.dooh,
+ category=Category.standard,
is_standard=True,
description="Transit and subway screen displays",
- dimensions="1920x1080",
accepts_3p_tags=False,
supported_macros=[*COMMON_MACROS, "SCREEN_ID", "VENUE_TYPE", "VENUE_LAT", "VENUE_LONG", "TRANSIT_LINE"],
- requirements=FormatRequirements(
- duration_seconds=15, # Longer dwell time in transit
- max_file_size_mb=5,
- ),
+ requirements={
+ "dimensions": "1920x1080",
+ "duration_seconds": 15,
+ "max_file_size_mb": 5,
+ },
assets_required=[
- AssetRequirement(
+ AssetsRequired(
asset_id="screen_image",
+ asset_type=AssetType.image,
asset_role="screen_image",
- asset_type="image",
required=True,
- width=1920,
- height=1080,
- acceptable_formats=["jpg", "png"],
- description="Transit screen content",
+ requirements={
+ "width": 1920,
+ "height": 1080,
+ "acceptable_formats": ["jpg", "png"],
+ "description": "Transit screen content",
+ },
),
],
),
@@ -1180,9 +1285,14 @@ def get_format_by_id(format_id: str) -> CreativeFormat | None:
def filter_formats(
format_ids: list[str] | None = None,
- type: str | None = None,
- asset_types: list[str] | None = None,
+ type: Type | str | None = None,
+ asset_types: list[AssetType | str] | None = None,
dimensions: str | None = None,
+ max_width: int | None = None,
+ max_height: int | None = None,
+ min_width: int | None = None,
+ min_height: int | None = None,
+ is_responsive: bool | None = None,
name_search: str | None = None,
) -> list[CreativeFormat]:
"""Filter formats based on criteria."""
@@ -1192,10 +1302,64 @@ def filter_formats(
results = [fmt for fmt in results if fmt.format_id in format_ids]
if type:
- results = [fmt for fmt in results if fmt.type == type]
+ # Handle both Type enum and string values
+ if isinstance(type, str):
+ results = [fmt for fmt in results if fmt.type.value == type]
+ else:
+ results = [fmt for fmt in results if fmt.type == type]
if dimensions:
- results = [fmt for fmt in results if fmt.dimensions == dimensions]
+ results = [fmt for fmt in results if fmt.requirements and fmt.requirements.get("dimensions") == dimensions]
+
+ # Dimension filtering - parse width and height from "WIDTHxHEIGHT" format
+ if any([max_width, max_height, min_width, min_height]):
+
+ def get_dimensions(fmt: CreativeFormat) -> tuple[int | None, int | None]:
+ """Extract width and height from format."""
+ if fmt.requirements and "dimensions" in fmt.requirements:
+ dims = fmt.requirements["dimensions"]
+ if isinstance(dims, str) and "x" in dims:
+ parts = dims.split("x")
+ if len(parts) == 2:
+ try:
+ return int(parts[0]), int(parts[1])
+ except ValueError:
+ pass
+ return None, None
+
+ filtered = []
+ for fmt in results:
+ width, height = get_dimensions(fmt)
+ if width is None or height is None:
+ continue # Skip formats without dimensions
+
+ if max_width is not None and width > max_width:
+ continue
+ if max_height is not None and height > max_height:
+ continue
+ if min_width is not None and width < min_width:
+ continue
+ if min_height is not None and height < min_height:
+ continue
+
+ filtered.append(fmt)
+ results = filtered
+
+ if is_responsive is not None:
+ # Filter for responsive formats (those without fixed dimensions)
+ if is_responsive:
+ results = [
+ fmt
+ for fmt in results
+ if not (fmt.requirements and "dimensions" in fmt.requirements and fmt.requirements["dimensions"])
+ ]
+ else:
+ # Filter for non-responsive (fixed dimension) formats
+ results = [
+ fmt
+ for fmt in results
+ if fmt.requirements and "dimensions" in fmt.requirements and fmt.requirements["dimensions"]
+ ]
if name_search:
search_lower = name_search.lower()
@@ -1203,10 +1367,27 @@ def filter_formats(
if asset_types:
# Filter to formats that include ALL specified asset types
+ def has_asset_type(req: AssetsRequired | object, target_type: AssetType | str) -> bool:
+ """Check if a requirement has the target asset type."""
+ if isinstance(req, AssetsRequired):
+ if isinstance(target_type, AssetType):
+ return req.asset_type == target_type
+ return req.asset_type.value == target_type
+ # AssetsRequired1 - check assets within the group
+ if hasattr(req, "assets"):
+ for asset in req.assets:
+ if isinstance(target_type, AssetType):
+ if asset.asset_type == target_type:
+ return True
+ elif asset.asset_type.value == target_type:
+ return True
+ return False
+
results = [
fmt
for fmt in results
- if all(any(req.asset_type == asset_type for req in fmt.assets_required) for asset_type in asset_types)
+ if fmt.assets_required
+ and all(any(has_asset_type(req, asset_type) for req in fmt.assets_required) for asset_type in asset_types)
]
return results
diff --git a/src/creative_agent/schemas/__init__.py b/src/creative_agent/schemas/__init__.py
index 52f1ae6..e19c6f9 100644
--- a/src/creative_agent/schemas/__init__.py
+++ b/src/creative_agent/schemas/__init__.py
@@ -1,54 +1,84 @@
-"""AdCP schemas for creative agent."""
-
-from .assets import (
- Asset,
- AudioAsset,
- HtmlAsset,
- ImageAsset,
- JavaScriptAsset,
- TextAsset,
- UrlAsset,
- VastTagAsset,
- VideoAsset,
- WebhookAsset,
+"""
+AdCP schemas for creative agent.
+
+This module re-exports official AdCP schemas from the auto-generated schemas_generated/
+directory, providing a clean interface for the rest of the codebase.
+
+All schemas are generated from https://adcontextprotocol.org/schemas/v1/
+"""
+
+# Asset schemas
+from ..schemas_generated._schemas_v1_core_assets_audio_asset_json import AudioAsset
+from ..schemas_generated._schemas_v1_core_assets_css_asset_json import CssAsset
+from ..schemas_generated._schemas_v1_core_assets_html_asset_json import HtmlAsset
+from ..schemas_generated._schemas_v1_core_assets_image_asset_json import ImageAsset
+from ..schemas_generated._schemas_v1_core_assets_javascript_asset_json import (
+ JavascriptAsset as JavaScriptAsset,
)
-from .format import (
- AssetRequirement,
- CreativeFormat,
- FormatRequirements,
- ListCreativeFormatsRequest,
+from ..schemas_generated._schemas_v1_core_assets_promoted_offerings_asset_json import (
+ PromotedOfferingsAsset,
+)
+from ..schemas_generated._schemas_v1_core_assets_text_asset_json import TextAsset
+from ..schemas_generated._schemas_v1_core_assets_url_asset_json import UrlAsset
+from ..schemas_generated._schemas_v1_core_assets_video_asset_json import VideoAsset
+
+# Preview schemas (using AdCP creative asset as manifest base)
+from ..schemas_generated._schemas_v1_core_creative_asset_json import CreativeAsset as CreativeManifest
+
+# Format schemas
+from ..schemas_generated._schemas_v1_core_format_json import Format as CreativeFormat
+from ..schemas_generated._schemas_v1_media_buy_list_creative_formats_response_json import (
ListCreativeFormatsResponse,
)
+
+# Build schemas (agent-specific, not part of AdCP)
+from .build import (
+ AssetReference,
+ BuildCreativeRequest,
+ BuildCreativeResponse,
+ CreativeOutput,
+ PreviewContext,
+ PreviewOptions,
+)
+
+# Format helpers (agent-specific, not part of AdCP)
+from .format_helpers import AssetRequirement, FormatRequirements
+
+# Manifest/Preview schemas - these need manual definitions as they're agent-specific
from .manifest import (
- CreativeManifest,
PreviewCreativeRequest,
PreviewCreativeResponse,
+ PreviewEmbedding,
+ PreviewHints,
PreviewInput,
PreviewVariant,
)
__all__ = [
- # Assets
- "Asset",
+ "AssetReference",
"AssetRequirement",
"AudioAsset",
- # Formats
+ "BuildCreativeRequest",
+ "BuildCreativeResponse",
"CreativeFormat",
- # Manifests
"CreativeManifest",
+ "CreativeOutput",
+ "CssAsset",
"FormatRequirements",
"HtmlAsset",
"ImageAsset",
"JavaScriptAsset",
- "ListCreativeFormatsRequest",
"ListCreativeFormatsResponse",
+ "PreviewContext",
"PreviewCreativeRequest",
"PreviewCreativeResponse",
+ "PreviewEmbedding",
+ "PreviewHints",
"PreviewInput",
+ "PreviewOptions",
"PreviewVariant",
+ "PromotedOfferingsAsset",
"TextAsset",
"UrlAsset",
- "VastTagAsset",
"VideoAsset",
- "WebhookAsset",
]
diff --git a/src/creative_agent/schemas/assets.py b/src/creative_agent/schemas/assets.py
deleted file mode 100644
index f890ac8..0000000
--- a/src/creative_agent/schemas/assets.py
+++ /dev/null
@@ -1,127 +0,0 @@
-"""Asset type schemas for AdCP creative manifests."""
-
-from typing import Any, Literal
-
-from pydantic import BaseModel
-
-
-class ImageAsset(BaseModel):
- """Image asset type."""
-
- asset_type: Literal["image"] = "image"
- url: str
- width: int
- height: int
- format: str # jpg, png, gif, webp, etc.
- file_size: int | None = None # bytes
- alt: str | None = None
-
-
-class VideoAsset(BaseModel):
- """Video asset type."""
-
- asset_type: Literal["video"] = "video"
- url: str
- width: int
- height: int
- duration_seconds: int
- format: str # mp4, mov, webm, etc.
- codec: str | None = None # h264, h265, vp9, av1
- bitrate_mbps: float | None = None
- file_size: int | None = None # bytes
-
-
-class AudioAsset(BaseModel):
- """Audio asset type."""
-
- asset_type: Literal["audio"] = "audio"
- url: str
- duration_seconds: int
- format: str # mp3, aac, wav, etc.
- codec: str | None = None
- bitrate_kbps: int | None = None
- sample_rate_hz: int | None = None
- channels: int | None = None # 1 = mono, 2 = stereo
- file_size: int | None = None # bytes
-
-
-class VastTagAsset(BaseModel):
- """VAST tag asset type."""
-
- asset_type: Literal["vast_tag"] = "vast_tag"
- content: str # VAST XML
- vast_version: str # "2.0", "3.0", "4.0", "4.1", "4.2"
- vpaid_enabled: bool | None = None
- duration_seconds: int | None = None
-
-
-class TextAsset(BaseModel):
- """Text asset type."""
-
- asset_type: Literal["text"] = "text"
- content: str
- length: int | None = None # character count
- format: Literal["plain", "html", "markdown"] | None = "plain"
-
-
-class UrlAsset(BaseModel):
- """URL asset type (for tracking pixels, clickthrough URLs, etc.)."""
-
- asset_type: Literal["url"] = "url"
- url: str
- url_purpose: str | None = None # clickthrough, impression, tracking, etc.
-
-
-class HtmlAsset(BaseModel):
- """HTML asset type (client-side third-party tags)."""
-
- asset_type: Literal["html"] = "html"
- content: str | None = None # HTML content (if inline)
- url: str | None = None # URL to HTML (if hosted)
- width: int | None = None
- height: int | None = None
- file_size: int | None = None # bytes
-
-
-class JavaScriptAsset(BaseModel):
- """JavaScript asset type (client-side third-party tags)."""
-
- asset_type: Literal["javascript"] = "javascript"
- content: str | None = None # JS code (if inline)
- url: str | None = None # URL to JS file (if hosted)
- inline: bool | None = None # Whether JS should be inlined
-
-
-class WebhookAsset(BaseModel):
- """Webhook asset type (server-side dynamic rendering)."""
-
- asset_type: Literal["webhook"] = "webhook"
- url: str
- method: Literal["GET", "POST"] | None = "POST"
- timeout_ms: int | None = 500
- response_type: str | None = "html" # html, json, image, etc.
- supported_macros: list[str] | None = None
- security: dict[str, Any] | None = None # HMAC, JWT, etc.
- fallback_required: bool | None = True
-
-
-class PromotedOfferingsAsset(BaseModel):
- """Promoted offerings asset type (brand context for generative creatives)."""
-
- asset_type: Literal["promoted_offerings"] = "promoted_offerings"
- offerings: list[dict[str, Any]] # Array of offering objects with name, description, assets
-
-
-# Union type for all asset types
-Asset = (
- ImageAsset
- | VideoAsset
- | AudioAsset
- | VastTagAsset
- | TextAsset
- | UrlAsset
- | HtmlAsset
- | JavaScriptAsset
- | WebhookAsset
- | PromotedOfferingsAsset
-)
diff --git a/src/creative_agent/schemas/format.py b/src/creative_agent/schemas/format.py
deleted file mode 100644
index a56ed33..0000000
--- a/src/creative_agent/schemas/format.py
+++ /dev/null
@@ -1,87 +0,0 @@
-"""Creative format schemas for AdCP."""
-
-from typing import Any, Literal
-
-from pydantic import BaseModel, HttpUrl
-
-
-class AssetRequirement(BaseModel):
- """Requirement for a single asset in a format."""
-
- asset_id: str # Identifier for this asset in the format
- asset_type: Literal["image", "video", "audio", "text", "html", "javascript", "url", "brand_manifest"]
- asset_role: str | None = None # e.g., "hero_image", "headline"
- required: bool = True
- requirements: dict[str, Any] | None = None # Technical requirements (dimensions, file size, duration, etc.)
- # Legacy fields for backward compatibility
- width: int | None = None
- height: int | None = None
- duration_seconds: int | None = None
- max_file_size_mb: float | None = None
- acceptable_formats: list[str] | None = None
- description: str | None = None
-
-
-class FormatRequirements(BaseModel):
- """Technical requirements for a format."""
-
- duration_seconds: int | None = None
- max_file_size_mb: float | None = None
- acceptable_formats: list[str] | None = None
- aspect_ratios: list[str] | None = None
- min_bitrate_kbps: int | None = None
- max_bitrate_kbps: int | None = None
-
-
-class CreativeFormat(BaseModel):
- """Complete creative format definition (AdCP v1.7.0)."""
-
- format_id: str
- agent_url: HttpUrl | str # Authoritative source for this format
- name: str
- type: Literal["audio", "video", "display", "native", "dooh", "rich_media", "universal"]
- category: Literal["standard", "custom"] | None = None
- is_standard: bool | None = None
- description: str | None = None
- preview_image: HttpUrl | None = None # Preview image URL for format browsing/discovery UI
- example_url: HttpUrl | None = None # URL to showcase page with examples and demos
- requirements: dict[str, Any] | FormatRequirements | None = (
- None # Technical specifications (dimensions, duration, file size, codecs)
- )
- assets_required: list[AssetRequirement]
- delivery: dict[str, Any] | None = None # Delivery method specifications (hosted, VAST, third-party tags)
- accepts_3p_tags: bool | None = False
- supported_macros: list[str] | None = None
- output_format_ids: list[str] | None = None # For generative formats: format IDs this can generate
- # Legacy fields for backward compatibility
- iab_specification: str | None = None # URL to IAB spec
- dimensions: str | None = None # "300x250", "728x90", etc.
-
-
-class ListCreativeFormatsRequest(BaseModel):
- """Request for list_creative_formats task (AdCP v1.6.0)."""
-
- adcp_version: str = "1.6.0"
- format_ids: list[str] | None = None
- type: Literal["audio", "video", "display", "dooh"] | None = None
- asset_types: list[Literal["image", "video", "audio", "text", "html", "javascript", "url"]] | None = None
- dimensions: str | None = None
- name_search: str | None = None
-
-
-class CreativeAgentInfo(BaseModel):
- """Information about a creative agent."""
-
- agent_url: HttpUrl
- agent_name: str | None = None
- capabilities: list[Literal["validation", "assembly", "generation", "preview"]] | None = None
-
-
-class ListCreativeFormatsResponse(BaseModel):
- """Response from list_creative_formats task (AdCP v1.7.0)."""
-
- adcp_version: str = "1.7.0"
- status: Literal["pending", "in_progress", "completed", "failed"] = "completed"
- formats: list[CreativeFormat]
- creative_agents: list[CreativeAgentInfo] | None = None
- errors: list[dict[str, Any]] | None = None
diff --git a/src/creative_agent/schemas/format_helpers.py b/src/creative_agent/schemas/format_helpers.py
new file mode 100644
index 0000000..7de384c
--- /dev/null
+++ b/src/creative_agent/schemas/format_helpers.py
@@ -0,0 +1,28 @@
+"""Helper classes for defining creative formats (not part of AdCP spec)."""
+
+from pydantic import BaseModel
+
+
+class AssetRequirement(BaseModel):
+ """Requirement for a single asset in a format."""
+
+ asset_role: str # e.g., "hero_image", "headline"
+ asset_type: str # image, video, audio, text, etc.
+ required: bool = True
+ width: int | None = None
+ height: int | None = None
+ duration_seconds: int | None = None
+ max_file_size_mb: float | None = None
+ acceptable_formats: list[str] | None = None
+ description: str | None = None
+
+
+class FormatRequirements(BaseModel):
+ """Technical requirements for a format."""
+
+ duration_seconds: int | None = None
+ max_file_size_mb: float | None = None
+ acceptable_formats: list[str] | None = None
+ aspect_ratios: list[str] | None = None
+ min_bitrate_kbps: int | None = None
+ max_bitrate_kbps: int | None = None
diff --git a/src/creative_agent/schemas/manifest.py b/src/creative_agent/schemas/manifest.py
index df84b67..d7d413f 100644
--- a/src/creative_agent/schemas/manifest.py
+++ b/src/creative_agent/schemas/manifest.py
@@ -4,16 +4,8 @@
from pydantic import BaseModel
-from .assets import Asset
-
-
-class CreativeManifest(BaseModel):
- """Creative manifest structure."""
-
- format_id: str
- promoted_offering: str | None = None # Product being advertised
- assets: dict[str, Asset] # Keyed by asset_role
- metadata: dict[str, Any] | None = None
+# CreativeManifest is imported from AdCP schemas via __init__.py
+# (uses CreativeAsset from AdCP as the base)
class PreviewInput(BaseModel):
@@ -28,7 +20,7 @@ class PreviewCreativeRequest(BaseModel):
"""Request for preview_creative task."""
format_id: str
- creative_manifest: CreativeManifest
+ creative_manifest: dict[str, Any] # AdCP CreativeAsset structure
inputs: list[PreviewInput] | None = None
template_id: str | None = None
brand_card: dict[str, Any] | None = None
diff --git a/src/creative_agent/schemas_generated/__init__.py b/src/creative_agent/schemas_generated/__init__.py
new file mode 100644
index 0000000..554821d
--- /dev/null
+++ b/src/creative_agent/schemas_generated/__init__.py
@@ -0,0 +1,15 @@
+"""
+Auto-generated Pydantic models from AdCP JSON schemas.
+
+⚠️ DO NOT EDIT FILES IN THIS DIRECTORY MANUALLY!
+
+Generated from: tests/schemas/v1/
+Generator: scripts/generate_schemas.py
+Tool: datamodel-code-generator + custom $ref resolution
+
+To regenerate:
+ python scripts/generate_schemas.py
+
+Source: https://adcontextprotocol.org/schemas/v1/
+AdCP Version: v2.4 (schemas v1)
+"""
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_adagents_json.py b/src/creative_agent/schemas_generated/_schemas_v1_adagents_json.py
new file mode 100644
index 0000000..5276750
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_adagents_json.py
@@ -0,0 +1,49 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_adagents_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Optional
+
+from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict, Field
+
+
+class AuthorizedAgent(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[AnyUrl, Field(description="The authorized agent's API endpoint URL")]
+ authorized_for: Annotated[
+ str,
+ Field(
+ description="Human-readable description of what this agent is authorized to sell (e.g., 'Official sales agent for our US and CA display inventory')",
+ max_length=500,
+ min_length=1,
+ ),
+ ]
+
+
+class AuthorizedSalesAgents(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ field_schema: Annotated[
+ Optional[str],
+ Field(
+ alias="$schema",
+ description="JSON Schema identifier for this adagents.json file",
+ ),
+ ] = "https://adcontextprotocol.org/schemas/v1/adagents.json"
+ authorized_agents: Annotated[
+ list[AuthorizedAgent],
+ Field(
+ description="Array of sales agents authorized to sell this publisher's inventory",
+ min_length=1,
+ ),
+ ]
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(
+ description="ISO 8601 timestamp indicating when this file was last updated (optional but recommended)"
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_audio_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_audio_asset_json.py
new file mode 100644
index 0000000..f683415
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_audio_asset_json.py
@@ -0,0 +1,25 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_audio-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class AudioAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["audio"]
+ url: Annotated[AnyUrl, Field(description="URL to the audio asset")]
+ duration_ms: Annotated[
+ Optional[int], Field(description="Audio duration in milliseconds", ge=0)
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="Audio file format (mp3, wav, aac, etc.)")
+ ] = None
+ bitrate_kbps: Annotated[
+ Optional[int], Field(description="Audio bitrate in kilobits per second", ge=1)
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_css_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_css_asset_json.py
new file mode 100644
index 0000000..86ef2f1
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_css_asset_json.py
@@ -0,0 +1,20 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_css-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class CssAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["css"]
+ content: Annotated[str, Field(description="CSS content")]
+ media: Annotated[
+ Optional[str],
+ Field(description="CSS media query context (e.g., 'screen', 'print')"),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_html_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_html_asset_json.py
new file mode 100644
index 0000000..8ecc3dd
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_html_asset_json.py
@@ -0,0 +1,19 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_html-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class HtmlAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["html"]
+ content: Annotated[str, Field(description="HTML content")]
+ version: Annotated[
+ Optional[str], Field(description="HTML version (e.g., 'HTML5')")
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_image_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_image_asset_json.py
new file mode 100644
index 0000000..e6938c1
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_image_asset_json.py
@@ -0,0 +1,29 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_image-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class ImageAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["image"]
+ url: Annotated[AnyUrl, Field(description="URL to the image asset")]
+ width: Annotated[
+ Optional[int], Field(description="Image width in pixels", ge=1)
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image height in pixels", ge=1)
+ ] = None
+ format: Annotated[
+ Optional[str],
+ Field(description="Image file format (jpg, png, gif, webp, etc.)"),
+ ] = None
+ alt_text: Annotated[
+ Optional[str], Field(description="Alternative text for accessibility")
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_javascript_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_javascript_asset_json.py
new file mode 100644
index 0000000..6352c64
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_javascript_asset_json.py
@@ -0,0 +1,26 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_javascript-asset_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Literal, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class ModuleType(Enum):
+ esm = "esm"
+ commonjs = "commonjs"
+ script = "script"
+
+
+class JavascriptAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["javascript"]
+ content: Annotated[str, Field(description="JavaScript content")]
+ module_type: Annotated[
+ Optional[ModuleType], Field(description="JavaScript module type")
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_promoted_offerings_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_promoted_offerings_asset_json.py
new file mode 100644
index 0000000..88f59dd
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_promoted_offerings_asset_json.py
@@ -0,0 +1,30 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_promoted-offerings-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class Colors(BaseModel):
+ primary: Optional[str] = None
+ secondary: Optional[str] = None
+ accent: Optional[str] = None
+
+
+class PromotedOfferingsAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["promoted_offerings"]
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="URL of the advertiser's brand or offering (e.g., https://retailer.com)"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand colors")] = None
+ fonts: Annotated[Optional[list[str]], Field(description="Brand fonts")] = None
+ tone: Annotated[Optional[str], Field(description="Brand tone/voice")] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_text_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_text_asset_json.py
new file mode 100644
index 0000000..9512827
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_text_asset_json.py
@@ -0,0 +1,22 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_text-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class TextAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["text"]
+ content: Annotated[str, Field(description="Text content")]
+ max_length: Annotated[
+ Optional[int], Field(description="Maximum character length constraint", ge=1)
+ ] = None
+ language: Annotated[
+ Optional[str], Field(description="Language code (e.g., 'en', 'es', 'fr')")
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_url_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_url_asset_json.py
new file mode 100644
index 0000000..9ef3051
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_url_asset_json.py
@@ -0,0 +1,19 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_url-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class UrlAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["url"]
+ url: Annotated[AnyUrl, Field(description="URL reference")]
+ description: Annotated[
+ Optional[str], Field(description="Description of what this URL points to")
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_assets_video_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_video_asset_json.py
new file mode 100644
index 0000000..ba70afe
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_assets_video_asset_json.py
@@ -0,0 +1,31 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_assets_video-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class VideoAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["video"]
+ url: Annotated[AnyUrl, Field(description="URL to the video asset")]
+ width: Annotated[
+ Optional[int], Field(description="Video width in pixels", ge=1)
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Video height in pixels", ge=1)
+ ] = None
+ duration_ms: Annotated[
+ Optional[int], Field(description="Video duration in milliseconds", ge=0)
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="Video file format (mp4, webm, mov, etc.)")
+ ] = None
+ bitrate_kbps: Annotated[
+ Optional[int], Field(description="Video bitrate in kilobits per second", ge=1)
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_brand_manifest_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_brand_manifest_json.py
new file mode 100644
index 0000000..e6700f3
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_brand_manifest_json.py
@@ -0,0 +1,480 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_brand-manifest_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional, Union
+
+from pydantic import (
+ AnyUrl,
+ AwareDatetime,
+ BaseModel,
+ ConfigDict,
+ EmailStr,
+ Field,
+ RootModel,
+)
+
+
+class Logo(BaseModel):
+ url: Annotated[AnyUrl, Field(description="URL to the logo asset")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Semantic tags describing the logo variant (e.g., 'dark', 'light', 'square', 'horizontal', 'icon')"
+ ),
+ ] = None
+ width: Annotated[Optional[int], Field(description="Logo width in pixels")] = None
+ height: Annotated[Optional[int], Field(description="Logo height in pixels")] = None
+
+
+class Colors(BaseModel):
+ primary: Annotated[
+ Optional[str],
+ Field(
+ description="Primary brand color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"
+ ),
+ ] = None
+ secondary: Annotated[
+ Optional[str],
+ Field(
+ description="Secondary brand color (hex format)",
+ pattern="^#[0-9A-Fa-f]{6}$",
+ ),
+ ] = None
+ accent: Annotated[
+ Optional[str],
+ Field(description="Accent color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+ background: Annotated[
+ Optional[str],
+ Field(description="Background color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+ text: Annotated[
+ Optional[str],
+ Field(description="Text color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+
+
+class Fonts(BaseModel):
+ primary: Annotated[Optional[str], Field(description="Primary font family name")] = (
+ None
+ )
+ secondary: Annotated[
+ Optional[str], Field(description="Secondary font family name")
+ ] = None
+ font_urls: Annotated[
+ Optional[list[AnyUrl]],
+ Field(description="URLs to web font files if using custom fonts"),
+ ] = None
+
+
+class AssetType(Enum):
+ image = "image"
+ video = "video"
+ audio = "audio"
+ text = "text"
+
+
+class Asset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class FeedFormat(Enum):
+ google_merchant_center = "google_merchant_center"
+ facebook_catalog = "facebook_catalog"
+ custom = "custom"
+
+
+class UpdateFrequency(Enum):
+ realtime = "realtime"
+ hourly = "hourly"
+ daily = "daily"
+ weekly = "weekly"
+
+
+class ProductCatalog(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class Disclaimer(BaseModel):
+ text: Annotated[str, Field(description="Disclaimer text")]
+ context: Annotated[
+ Optional[str],
+ Field(
+ description="When this disclaimer applies (e.g., 'financial_products', 'health_claims', 'all')"
+ ),
+ ] = None
+ required: Annotated[
+ Optional[bool], Field(description="Whether this disclaimer must appear")
+ ] = True
+
+
+class Contact(BaseModel):
+ email: Annotated[Optional[EmailStr], Field(description="Contact email")] = None
+ phone: Annotated[Optional[str], Field(description="Contact phone number")] = None
+
+
+class Metadata(BaseModel):
+ created_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this brand manifest was created"),
+ ] = None
+ updated_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this brand manifest was last updated"),
+ ] = None
+ version: Annotated[
+ Optional[str], Field(description="Brand card version number")
+ ] = None
+
+
+class BrandManifest1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ AnyUrl,
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ]
+ name: Annotated[Optional[str], Field(description="Brand or business name")] = None
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Asset3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class ProductCatalog3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class BrandManifest2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ] = None
+ name: Annotated[str, Field(description="Brand or business name")]
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset3]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog3],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class BrandManifest(RootModel[Union[BrandManifest1, BrandManifest2]]):
+ root: Annotated[
+ Union[BrandManifest1, BrandManifest2],
+ Field(
+ 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.",
+ examples=[
+ {
+ "description": "Example with both URL and name",
+ "data": {
+ "url": "https://bobsfunburgers.com",
+ "name": "Bob's Fun Burgers",
+ },
+ },
+ {
+ "description": "Example: white-label brand without dedicated URL",
+ "data": {
+ "name": "Great Value",
+ "colors": {"primary": "#0071CE", "secondary": "#FFC220"},
+ "tone": "affordable and trustworthy",
+ },
+ },
+ {
+ "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"],
+ "width": 512,
+ "height": 512,
+ },
+ {
+ "url": "https://cdn.acmecorp.com/logo-horizontal-light.png",
+ "tags": ["light", "horizontal"],
+ "width": 1200,
+ "height": 400,
+ },
+ ],
+ "colors": {
+ "primary": "#FF6B35",
+ "secondary": "#004E89",
+ "accent": "#F7931E",
+ "background": "#FFFFFF",
+ "text": "#1A1A1A",
+ },
+ "fonts": {"primary": "Helvetica Neue", "secondary": "Georgia"},
+ "tone": "professional and trustworthy",
+ "tagline": "Innovation You Can Trust",
+ "assets": [
+ {
+ "asset_id": "hero_winter_2024",
+ "asset_type": "image",
+ "url": "https://cdn.acmecorp.com/hero-winter-2024.jpg",
+ "tags": ["hero", "winter", "holiday", "lifestyle"],
+ "name": "Winter Campaign Hero",
+ "width": 1920,
+ "height": 1080,
+ "format": "jpg",
+ },
+ {
+ "asset_id": "product_video_30s",
+ "asset_type": "video",
+ "url": "https://cdn.acmecorp.com/product-demo-30s.mp4",
+ "tags": ["product", "demo", "30s"],
+ "name": "Product Demo 30 Second",
+ "width": 1920,
+ "height": 1080,
+ "duration_seconds": 30,
+ "format": "mp4",
+ },
+ ],
+ "product_catalog": {
+ "feed_url": "https://acmecorp.com/products.xml",
+ "feed_format": "google_merchant_center",
+ "categories": [
+ "electronics/computers",
+ "electronics/accessories",
+ ],
+ "last_updated": "2024-03-15T10:00:00Z",
+ "update_frequency": "hourly",
+ },
+ "disclaimers": [
+ {
+ "text": "Results may vary. Consult a professional before use.",
+ "context": "health_claims",
+ "required": True,
+ }
+ ],
+ "industry": "technology",
+ "target_audience": "business decision-makers aged 35-55",
+ },
+ },
+ ],
+ title="Brand Manifest",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_brand_manifest_ref_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_brand_manifest_ref_json.py
new file mode 100644
index 0000000..cf100d7
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_brand_manifest_ref_json.py
@@ -0,0 +1,406 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_brand-manifest-ref_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional, Union
+
+from pydantic import (
+ AnyUrl,
+ AwareDatetime,
+ BaseModel,
+ ConfigDict,
+ EmailStr,
+ Field,
+ RootModel,
+)
+
+
+class Logo(BaseModel):
+ url: Annotated[AnyUrl, Field(description="URL to the logo asset")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Semantic tags describing the logo variant (e.g., 'dark', 'light', 'square', 'horizontal', 'icon')"
+ ),
+ ] = None
+ width: Annotated[Optional[int], Field(description="Logo width in pixels")] = None
+ height: Annotated[Optional[int], Field(description="Logo height in pixels")] = None
+
+
+class Colors(BaseModel):
+ primary: Annotated[
+ Optional[str],
+ Field(
+ description="Primary brand color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"
+ ),
+ ] = None
+ secondary: Annotated[
+ Optional[str],
+ Field(
+ description="Secondary brand color (hex format)",
+ pattern="^#[0-9A-Fa-f]{6}$",
+ ),
+ ] = None
+ accent: Annotated[
+ Optional[str],
+ Field(description="Accent color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+ background: Annotated[
+ Optional[str],
+ Field(description="Background color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+ text: Annotated[
+ Optional[str],
+ Field(description="Text color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+
+
+class Fonts(BaseModel):
+ primary: Annotated[Optional[str], Field(description="Primary font family name")] = (
+ None
+ )
+ secondary: Annotated[
+ Optional[str], Field(description="Secondary font family name")
+ ] = None
+ font_urls: Annotated[
+ Optional[list[AnyUrl]],
+ Field(description="URLs to web font files if using custom fonts"),
+ ] = None
+
+
+class AssetType(Enum):
+ image = "image"
+ video = "video"
+ audio = "audio"
+ text = "text"
+
+
+class Asset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class FeedFormat(Enum):
+ google_merchant_center = "google_merchant_center"
+ facebook_catalog = "facebook_catalog"
+ custom = "custom"
+
+
+class UpdateFrequency(Enum):
+ realtime = "realtime"
+ hourly = "hourly"
+ daily = "daily"
+ weekly = "weekly"
+
+
+class ProductCatalog(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class Disclaimer(BaseModel):
+ text: Annotated[str, Field(description="Disclaimer text")]
+ context: Annotated[
+ Optional[str],
+ Field(
+ description="When this disclaimer applies (e.g., 'financial_products', 'health_claims', 'all')"
+ ),
+ ] = None
+ required: Annotated[
+ Optional[bool], Field(description="Whether this disclaimer must appear")
+ ] = True
+
+
+class Contact(BaseModel):
+ email: Annotated[Optional[EmailStr], Field(description="Contact email")] = None
+ phone: Annotated[Optional[str], Field(description="Contact phone number")] = None
+
+
+class Metadata(BaseModel):
+ created_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this brand manifest was created"),
+ ] = None
+ updated_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this brand manifest was last updated"),
+ ] = None
+ version: Annotated[
+ Optional[str], Field(description="Brand card version number")
+ ] = None
+
+
+class BrandManifestReference1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ AnyUrl,
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ]
+ name: Annotated[Optional[str], Field(description="Brand or business name")] = None
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Asset1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class ProductCatalog1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class BrandManifestReference2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ] = None
+ name: Annotated[str, Field(description="Brand or business name")]
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset1]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog1],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class BrandManifestReference(
+ RootModel[Union[Union[BrandManifestReference1, BrandManifestReference2], AnyUrl]]
+):
+ root: Annotated[
+ Union[Union[BrandManifestReference1, BrandManifestReference2], AnyUrl],
+ Field(
+ description="Brand manifest provided either as an inline object or a URL string pointing to a hosted manifest",
+ examples=[
+ {
+ "description": "Inline brand manifest",
+ "data": {
+ "url": "https://acmecorp.com",
+ "name": "ACME Corporation",
+ "colors": {"primary": "#FF6B35"},
+ },
+ },
+ {
+ "description": "URL string reference to hosted manifest",
+ "data": "https://cdn.acmecorp.com/brand-manifest.json",
+ },
+ ],
+ title="Brand Manifest Reference",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_budget_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_budget_json.py
new file mode 100644
index 0000000..b869a19
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_budget_json.py
@@ -0,0 +1,33 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_budget_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class Pacing(Enum):
+ even = "even"
+ asap = "asap"
+ front_loaded = "front_loaded"
+
+
+class Budget(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ total: Annotated[float, Field(description="Total budget amount", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ pacing: Annotated[
+ Optional[Pacing], Field(description="Budget pacing strategy", title="Pacing")
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_creative_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_creative_asset_json.py
new file mode 100644
index 0000000..7443110
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_creative_asset_json.py
@@ -0,0 +1,243 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_creative-asset_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Literal, Optional, Union
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class FormatId(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ agent_url: Annotated[
+ AnyUrl,
+ Field(
+ 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: Annotated[
+ str,
+ Field(
+ description="Format identifier within the agent's namespace (e.g., 'display_300x250', 'video_standard_30s')",
+ pattern="^[a-zA-Z0-9_-]+$",
+ ),
+ ]
+
+
+class Assets(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["image"]
+ url: Annotated[AnyUrl, Field(description="URL to the image asset")]
+ width: Annotated[
+ Optional[int], Field(description="Image width in pixels", ge=1)
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image height in pixels", ge=1)
+ ] = None
+ format: Annotated[
+ Optional[str],
+ Field(description="Image file format (jpg, png, gif, webp, etc.)"),
+ ] = None
+ alt_text: Annotated[
+ Optional[str], Field(description="Alternative text for accessibility")
+ ] = None
+
+
+class Assets1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["video"]
+ url: Annotated[AnyUrl, Field(description="URL to the video asset")]
+ width: Annotated[
+ Optional[int], Field(description="Video width in pixels", ge=1)
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Video height in pixels", ge=1)
+ ] = None
+ duration_ms: Annotated[
+ Optional[int], Field(description="Video duration in milliseconds", ge=0)
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="Video file format (mp4, webm, mov, etc.)")
+ ] = None
+ bitrate_kbps: Annotated[
+ Optional[int], Field(description="Video bitrate in kilobits per second", ge=1)
+ ] = None
+
+
+class Assets2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["audio"]
+ url: Annotated[AnyUrl, Field(description="URL to the audio asset")]
+ duration_ms: Annotated[
+ Optional[int], Field(description="Audio duration in milliseconds", ge=0)
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="Audio file format (mp3, wav, aac, etc.)")
+ ] = None
+ bitrate_kbps: Annotated[
+ Optional[int], Field(description="Audio bitrate in kilobits per second", ge=1)
+ ] = None
+
+
+class Assets3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["text"]
+ content: Annotated[str, Field(description="Text content")]
+ max_length: Annotated[
+ Optional[int], Field(description="Maximum character length constraint", ge=1)
+ ] = None
+ language: Annotated[
+ Optional[str], Field(description="Language code (e.g., 'en', 'es', 'fr')")
+ ] = None
+
+
+class Assets4(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["html"]
+ content: Annotated[str, Field(description="HTML content")]
+ version: Annotated[
+ Optional[str], Field(description="HTML version (e.g., 'HTML5')")
+ ] = None
+
+
+class Assets5(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["css"]
+ content: Annotated[str, Field(description="CSS content")]
+ media: Annotated[
+ Optional[str],
+ Field(description="CSS media query context (e.g., 'screen', 'print')"),
+ ] = None
+
+
+class ModuleType(Enum):
+ esm = "esm"
+ commonjs = "commonjs"
+ script = "script"
+
+
+class Assets6(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["javascript"]
+ content: Annotated[str, Field(description="JavaScript content")]
+ module_type: Annotated[
+ Optional[ModuleType], Field(description="JavaScript module type")
+ ] = None
+
+
+class Colors(BaseModel):
+ primary: Optional[str] = None
+ secondary: Optional[str] = None
+ accent: Optional[str] = None
+
+
+class Assets7(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["promoted_offerings"]
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="URL of the advertiser's brand or offering (e.g., https://retailer.com)"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand colors")] = None
+ fonts: Annotated[Optional[list[str]], Field(description="Brand fonts")] = None
+ tone: Annotated[Optional[str], Field(description="Brand tone/voice")] = None
+
+
+class Assets8(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["url"]
+ url: Annotated[AnyUrl, Field(description="URL reference")]
+ description: Annotated[
+ Optional[str], Field(description="Description of what this URL points to")
+ ] = None
+
+
+class Input(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ name: Annotated[
+ str, Field(description="Human-readable name for this preview variant")
+ ]
+ macros: Annotated[
+ Optional[dict[str, str]],
+ Field(description="Macro values to apply for this preview"),
+ ] = None
+ context_description: Annotated[
+ Optional[str],
+ Field(
+ description="Natural language description of the context for AI-generated content"
+ ),
+ ] = None
+
+
+class CreativeAsset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ creative_id: Annotated[str, Field(description="Unique identifier for the creative")]
+ name: Annotated[str, Field(description="Human-readable creative name")]
+ format_id: Annotated[
+ FormatId,
+ Field(
+ description="Structured format identifier with agent URL and format name",
+ title="Format ID",
+ ),
+ ]
+ assets: Annotated[
+ dict[
+ str,
+ Union[
+ Assets,
+ Assets1,
+ Assets2,
+ Assets3,
+ Assets4,
+ Assets5,
+ Assets6,
+ Assets7,
+ Assets8,
+ ],
+ ],
+ Field(description="Assets required by the format, keyed by asset_role"),
+ ]
+ inputs: Annotated[
+ Optional[list[Input]],
+ Field(
+ description="Preview contexts for generative formats - defines what scenarios to generate previews for"
+ ),
+ ] = None
+ tags: Annotated[
+ Optional[list[str]],
+ Field(description="User-defined tags for organization and searchability"),
+ ] = None
+ approved: Annotated[
+ Optional[bool],
+ Field(
+ description="For generative creatives: set to true to approve and finalize, false to request regeneration with updated assets/message. Omit for non-generative creatives."
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_creative_assignment_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_creative_assignment_json.py
new file mode 100644
index 0000000..91e2464
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_creative_assignment_json.py
@@ -0,0 +1,19 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_creative-assignment_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class CreativeAssignment(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ creative_id: Annotated[str, Field(description="Unique identifier for the creative")]
+ weight: Annotated[
+ Optional[float],
+ Field(description="Delivery weight for this creative", ge=0.0, le=100.0),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_creative_library_item_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_creative_library_item_json.py
new file mode 100644
index 0000000..a76ebde
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_creative_library_item_json.py
@@ -0,0 +1,179 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_creative-library-item_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional, Union
+
+from pydantic import AnyUrl, AwareDatetime, BaseModel, ConfigDict, Field
+
+
+class Status(Enum):
+ processing = "processing"
+ approved = "approved"
+ rejected = "rejected"
+ pending_review = "pending_review"
+
+
+class PerformanceMetrics(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ total_impressions: Annotated[
+ Optional[int], Field(description="Total impressions delivered", ge=0)
+ ] = None
+ total_clicks: Annotated[
+ Optional[int], Field(description="Total clicks generated", ge=0)
+ ] = None
+ average_ctr: Annotated[
+ Optional[float],
+ Field(
+ description="Average click-through rate across assignments", ge=0.0, le=1.0
+ ),
+ ] = None
+ last_served: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this creative last served an impression"),
+ ] = None
+
+
+class Status1(Enum):
+ approved = "approved"
+ pending = "pending"
+ rejected = "rejected"
+ flagged = "flagged"
+
+
+class Compliance(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ status: Annotated[Status1, Field(description="Compliance status")]
+ issues: Annotated[
+ Optional[list[str]], Field(description="Array of compliance issues")
+ ] = None
+ reviewed_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When compliance review was completed"),
+ ] = None
+
+
+class Assets(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Annotated[
+ str,
+ Field(
+ description="Type of asset. Common types: headline, body_text, thumbnail_image, product_image, featured_image, logo, cta_text, price_text, sponsor_name, author_name, click_url"
+ ),
+ ]
+ asset_id: Annotated[
+ str, Field(description="Unique identifier for the asset within the creative")
+ ]
+ content_uri: Annotated[
+ AnyUrl, Field(description="URL for media assets (images, videos, etc.)")
+ ]
+ content: Annotated[
+ Optional[Union[str, list[str]]],
+ Field(
+ description="Text content for text-based assets like headlines, body text, CTA text, etc."
+ ),
+ ] = None
+
+
+class Assets10(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Annotated[
+ str,
+ Field(
+ description="Type of asset. Common types: headline, body_text, thumbnail_image, product_image, featured_image, logo, cta_text, price_text, sponsor_name, author_name, click_url"
+ ),
+ ]
+ asset_id: Annotated[
+ str, Field(description="Unique identifier for the asset within the creative")
+ ]
+ content_uri: Annotated[
+ Optional[AnyUrl],
+ Field(description="URL for media assets (images, videos, etc.)"),
+ ] = None
+ content: Annotated[
+ Union[str, list[str]],
+ Field(
+ description="Text content for text-based assets like headlines, body text, CTA text, etc."
+ ),
+ ]
+
+
+class CreativeLibraryItem(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ creative_id: Annotated[str, Field(description="Unique identifier for the creative")]
+ name: Annotated[str, Field(description="Human-readable creative name")]
+ format: Annotated[
+ str, Field(description="Creative format type (e.g., video, audio, display)")
+ ]
+ status: Annotated[
+ Status, Field(description="Status of a creative asset", title="Creative Status")
+ ]
+ platform_id: Annotated[
+ Optional[str],
+ Field(description="Platform-specific ID assigned to the creative"),
+ ] = None
+ created_date: Annotated[
+ AwareDatetime,
+ Field(description="When the creative was uploaded to the library"),
+ ]
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the creative was last modified"),
+ ] = None
+ media_url: Annotated[
+ Optional[AnyUrl], Field(description="URL of the creative file")
+ ] = None
+ click_url: Annotated[
+ Optional[AnyUrl], Field(description="Landing page URL for the creative")
+ ] = None
+ duration: Annotated[
+ Optional[float],
+ Field(description="Duration in milliseconds (for video/audio)", ge=0.0),
+ ] = None
+ width: Annotated[
+ Optional[float],
+ Field(description="Width in pixels (for video/display)", ge=0.0),
+ ] = None
+ height: Annotated[
+ Optional[float],
+ Field(description="Height in pixels (for video/display)", ge=0.0),
+ ] = None
+ file_size: Annotated[
+ Optional[int], Field(description="File size in bytes", ge=0)
+ ] = None
+ assignments: Annotated[
+ Optional[list[str]],
+ Field(description="Current package assignments for this creative"),
+ ] = None
+ assignment_count: Annotated[
+ Optional[int], Field(description="Number of active package assignments", ge=0)
+ ] = None
+ performance_metrics: Annotated[
+ Optional[PerformanceMetrics],
+ Field(description="Aggregated performance data across all assignments"),
+ ] = None
+ compliance: Annotated[
+ Optional[Compliance], Field(description="Compliance review status")
+ ] = None
+ review_feedback: Annotated[
+ Optional[str], Field(description="Latest feedback from platform review")
+ ] = None
+ tags: Annotated[
+ Optional[list[str]], Field(description="User-defined tags for organization")
+ ] = None
+ assets: Annotated[
+ Optional[list[Union[Assets, Assets10]]],
+ Field(description="Sub-assets for multi-asset formats like carousels"),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_creative_policy_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_creative_policy_json.py
new file mode 100644
index 0000000..3fcd0b9
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_creative_policy_json.py
@@ -0,0 +1,32 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_creative-policy_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class CoBranding(Enum):
+ required = "required"
+ optional = "optional"
+ none = "none"
+
+
+class LandingPage(Enum):
+ any = "any"
+ retailer_site_only = "retailer_site_only"
+ must_include_retailer = "must_include_retailer"
+
+
+class CreativePolicy(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ co_branding: Annotated[CoBranding, Field(description="Co-branding requirement")]
+ landing_page: Annotated[LandingPage, Field(description="Landing page requirements")]
+ templates_available: Annotated[
+ bool, Field(description="Whether creative templates are provided")
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_delivery_metrics_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_delivery_metrics_json.py
new file mode 100644
index 0000000..27b6dec
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_delivery_metrics_json.py
@@ -0,0 +1,147 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_delivery-metrics_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class QuartileData(BaseModel):
+ q1_views: Annotated[
+ Optional[float], Field(description="25% completion views", ge=0.0)
+ ] = None
+ q2_views: Annotated[
+ Optional[float], Field(description="50% completion views", ge=0.0)
+ ] = None
+ q3_views: Annotated[
+ Optional[float], Field(description="75% completion views", ge=0.0)
+ ] = None
+ q4_views: Annotated[
+ Optional[float], Field(description="100% completion views", ge=0.0)
+ ] = None
+
+
+class VenueBreakdownItem(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ venue_id: Annotated[str, Field(description="Venue identifier")]
+ venue_name: Annotated[
+ Optional[str], Field(description="Human-readable venue name")
+ ] = None
+ venue_type: Annotated[
+ Optional[str],
+ Field(
+ description="Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')"
+ ),
+ ] = None
+ impressions: Annotated[
+ int, Field(description="Impressions delivered at this venue", ge=0)
+ ]
+ loop_plays: Annotated[
+ Optional[int], Field(description="Loop plays at this venue", ge=0)
+ ] = None
+ screens_used: Annotated[
+ Optional[int], Field(description="Number of screens used at this venue", ge=0)
+ ] = None
+
+
+class DoohMetrics(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ loop_plays: Annotated[
+ Optional[int], Field(description="Number of times ad played in rotation", ge=0)
+ ] = None
+ screens_used: Annotated[
+ Optional[int],
+ Field(description="Number of unique screens displaying the ad", ge=0),
+ ] = None
+ screen_time_seconds: Annotated[
+ Optional[int], Field(description="Total display time in seconds", ge=0)
+ ] = None
+ sov_achieved: Annotated[
+ Optional[float],
+ Field(
+ description="Actual share of voice delivered (0.0 to 1.0)", ge=0.0, le=1.0
+ ),
+ ] = None
+ calculation_notes: Annotated[
+ Optional[str],
+ Field(description="Explanation of how DOOH impressions were calculated"),
+ ] = None
+ venue_breakdown: Annotated[
+ Optional[list[VenueBreakdownItem]],
+ Field(description="Per-venue performance breakdown"),
+ ] = None
+
+
+class DeliveryMetrics(BaseModel):
+ model_config = ConfigDict(
+ extra="allow",
+ )
+ impressions: Annotated[
+ Optional[float], Field(description="Impressions delivered", ge=0.0)
+ ] = None
+ spend: Annotated[Optional[float], Field(description="Amount spent", ge=0.0)] = None
+ clicks: Annotated[Optional[float], Field(description="Total clicks", ge=0.0)] = None
+ ctr: Annotated[
+ Optional[float],
+ Field(description="Click-through rate (clicks/impressions)", ge=0.0, le=1.0),
+ ] = None
+ views: Annotated[
+ Optional[float], Field(description="Views at threshold (for CPV)", ge=0.0)
+ ] = None
+ completed_views: Annotated[
+ Optional[float], Field(description="100% completions (for CPCV)", ge=0.0)
+ ] = None
+ video_completions: Annotated[
+ Optional[float],
+ Field(description="DEPRECATED: Use completed_views instead", ge=0.0),
+ ] = None
+ completion_rate: Annotated[
+ Optional[float],
+ Field(
+ description="Completion rate (completed_views/impressions)", ge=0.0, le=1.0
+ ),
+ ] = None
+ conversions: Annotated[
+ Optional[float],
+ Field(
+ description="Conversions (reserved for future CPA pricing support)", ge=0.0
+ ),
+ ] = None
+ leads: Annotated[
+ Optional[float],
+ Field(
+ description="Leads generated (reserved for future CPL pricing support)",
+ ge=0.0,
+ ),
+ ] = None
+ grps: Annotated[
+ Optional[float],
+ Field(description="Gross Rating Points delivered (for CPP)", ge=0.0),
+ ] = None
+ reach: Annotated[
+ Optional[float],
+ Field(
+ description="Unique reach - units depend on measurement provider (e.g., individuals, households, devices, cookies). See delivery_measurement.provider for methodology.",
+ ge=0.0,
+ ),
+ ] = None
+ frequency: Annotated[
+ Optional[float],
+ Field(
+ description="Average frequency per individual (typically measured over campaign duration, but can vary by measurement provider)",
+ ge=0.0,
+ ),
+ ] = None
+ quartile_data: Annotated[
+ Optional[QuartileData], Field(description="Video quartile completion data")
+ ] = None
+ dooh_metrics: Annotated[
+ Optional[DoohMetrics],
+ Field(description="DOOH-specific metrics (only included for DOOH campaigns)"),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_error_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_error_json.py
new file mode 100644
index 0000000..eeda95a
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_error_json.py
@@ -0,0 +1,32 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_error_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Any, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class Error(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ code: Annotated[str, Field(description="Error code for programmatic handling")]
+ message: Annotated[str, Field(description="Human-readable error message")]
+ field: Annotated[
+ Optional[str],
+ Field(
+ description="Field path associated with the error (e.g., 'packages[0].targeting')"
+ ),
+ ] = None
+ suggestion: Annotated[
+ Optional[str], Field(description="Suggested fix for the error")
+ ] = None
+ retry_after: Annotated[
+ Optional[float],
+ Field(description="Seconds to wait before retrying the operation", ge=0.0),
+ ] = None
+ details: Annotated[
+ Optional[Any], Field(description="Additional task-specific error details")
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_format_id_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_format_id_json.py
new file mode 100644
index 0000000..11676de
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_format_id_json.py
@@ -0,0 +1,27 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_format-id_json.json
+
+from __future__ import annotations
+
+from typing import Annotated
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class FormatId(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ agent_url: Annotated[
+ AnyUrl,
+ Field(
+ 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: Annotated[
+ str,
+ Field(
+ description="Format identifier within the agent's namespace (e.g., 'display_300x250', 'video_standard_30s')",
+ pattern="^[a-zA-Z0-9_-]+$",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_format_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_format_json.py
new file mode 100644
index 0000000..bc0c6cf
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_format_json.py
@@ -0,0 +1,181 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_format_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional, Union
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class Type(Enum):
+ audio = "audio"
+ video = "video"
+ display = "display"
+ native = "native"
+ dooh = "dooh"
+ rich_media = "rich_media"
+ universal = "universal"
+
+
+class Category(Enum):
+ standard = "standard"
+ custom = "custom"
+
+
+class AssetType(Enum):
+ image = "image"
+ video = "video"
+ audio = "audio"
+ text = "text"
+ html = "html"
+ javascript = "javascript"
+ url = "url"
+ brand_manifest = "brand_manifest"
+
+
+class AssetsRequired(BaseModel):
+ asset_id: Annotated[
+ str, Field(description="Identifier for this asset in the format")
+ ]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ asset_role: Annotated[
+ Optional[str],
+ Field(
+ description="Purpose of this asset (e.g., 'hero_image', 'logo', 'headline', 'cta_button')"
+ ),
+ ] = None
+ required: Annotated[
+ Optional[bool], Field(description="Whether this asset is required")
+ ] = None
+ requirements: Annotated[
+ Optional[dict[str, Any]],
+ Field(
+ description="Technical requirements for this asset (dimensions, file size, duration, etc.)"
+ ),
+ ] = None
+
+
+class Repeatable(Enum):
+ boolean_true = True
+
+
+class Asset(BaseModel):
+ asset_id: Annotated[
+ str, Field(description="Identifier for this asset within the group")
+ ]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ asset_role: Annotated[Optional[str], Field(description="Purpose of this asset")] = (
+ None
+ )
+ required: Annotated[
+ Optional[bool],
+ Field(description="Whether this asset is required in each repetition"),
+ ] = None
+ requirements: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Technical requirements for this asset"),
+ ] = None
+
+
+class AssetsRequired1(BaseModel):
+ asset_group_id: Annotated[
+ str,
+ Field(
+ description="Identifier for this asset group (e.g., 'product', 'slide', 'card')"
+ ),
+ ]
+ repeatable: Annotated[
+ Repeatable, Field(description="Indicates this is a repeatable asset group")
+ ]
+ min_count: Annotated[
+ int, Field(description="Minimum number of repetitions required", ge=1)
+ ]
+ max_count: Annotated[
+ int, Field(description="Maximum number of repetitions allowed", ge=1)
+ ]
+ assets: Annotated[
+ list[Asset], Field(description="Assets within each repetition of this group")
+ ]
+
+
+class Format(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ format_id: Annotated[str, Field(description="Unique identifier for the format")]
+ agent_url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Base URL of the agent that provides this format (authoritative source). E.g., 'https://reference.adcp.org', 'https://dco.example.com'"
+ ),
+ ] = None
+ name: Annotated[str, Field(description="Human-readable format name")]
+ description: Annotated[
+ Optional[str],
+ Field(
+ description="Plain text explanation of what this format does and what assets it requires"
+ ),
+ ] = None
+ preview_image: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Optional preview image URL for format browsing/discovery UI"
+ ),
+ ] = None
+ example_url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Optional URL to showcase page with examples and interactive demos of this format"
+ ),
+ ] = None
+ type: Annotated[
+ Type,
+ Field(
+ description="Media type of this format - determines rendering method and asset requirements"
+ ),
+ ]
+ category: Annotated[Optional[Category], Field(description="Format category")] = None
+ is_standard: Annotated[
+ Optional[bool],
+ Field(
+ description="Whether this format follows IAB specifications or AdCP standard format definitions (found in /schemas/v1/standard-formats/)"
+ ),
+ ] = None
+ requirements: Annotated[
+ Optional[dict[str, Any]],
+ Field(
+ description="Technical specifications for this format (e.g., dimensions, duration, file size limits, codecs)"
+ ),
+ ] = None
+ assets_required: Annotated[
+ Optional[list[Union[AssetsRequired, AssetsRequired1]]],
+ Field(
+ description="Array of required assets or asset groups for this format. Can contain individual assets or repeatable asset sequences (e.g., carousel products, slideshow frames)."
+ ),
+ ] = None
+ delivery: Annotated[
+ Optional[dict[str, Any]],
+ Field(
+ description="Delivery method specifications (e.g., hosted, VAST, third-party tags)"
+ ),
+ ] = None
+ accepts_3p_tags: Annotated[
+ Optional[bool],
+ Field(
+ description="Whether this format can accept third-party served creative tags as an alternative to hosted assets"
+ ),
+ ] = None
+ supported_macros: Annotated[
+ Optional[list[str]],
+ Field(
+ description="List of universal macros supported by this format (e.g., MEDIA_BUY_ID, CACHEBUSTER, DEVICE_ID). Used for validation and developer tooling."
+ ),
+ ] = None
+ output_format_ids: Annotated[
+ Optional[list[str]],
+ Field(
+ description="For generative formats: array of format IDs that this format can generate. When a format accepts inputs like brand_manifest and message, this specifies what concrete output formats can be produced (e.g., a generative banner format might output standard image banner formats)."
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_frequency_cap_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_frequency_cap_json.py
new file mode 100644
index 0000000..18ffdd3
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_frequency_cap_json.py
@@ -0,0 +1,17 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_frequency-cap_json.json
+
+from __future__ import annotations
+
+from typing import Annotated
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class FrequencyCap(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ suppress_minutes: Annotated[
+ float, Field(description="Minutes to suppress after impression", ge=0.0)
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_measurement_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_measurement_json.py
new file mode 100644
index 0000000..b592a84
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_measurement_json.py
@@ -0,0 +1,39 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_measurement_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class Measurement(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ type: Annotated[
+ str,
+ Field(
+ description="Type of measurement",
+ examples=["incremental_sales_lift", "brand_lift", "foot_traffic"],
+ ),
+ ]
+ attribution: Annotated[
+ str,
+ Field(
+ description="Attribution methodology",
+ examples=["deterministic_purchase", "probabilistic"],
+ ),
+ ]
+ window: Annotated[
+ Optional[str],
+ Field(description="Attribution window", examples=["30_days", "7_days"]),
+ ] = None
+ reporting: Annotated[
+ str,
+ Field(
+ description="Reporting frequency and format",
+ examples=["weekly_dashboard", "real_time_api"],
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_media_buy_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_media_buy_json.py
new file mode 100644
index 0000000..14e509f
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_media_buy_json.py
@@ -0,0 +1,163 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_media-buy_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional
+
+from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, RootModel
+
+
+class Status(Enum):
+ pending_activation = "pending_activation"
+ active = "active"
+ paused = "paused"
+ completed = "completed"
+
+
+class GeoCountryAnyOfItem(RootModel[str]):
+ root: Annotated[str, Field(pattern="^[A-Z]{2}$")]
+
+
+class FrequencyCap(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ suppress_minutes: Annotated[
+ float, Field(description="Minutes to suppress after impression", ge=0.0)
+ ]
+
+
+class TargetingOverlay(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ geo_country_any_of: Annotated[
+ Optional[list[GeoCountryAnyOfItem]],
+ Field(
+ description="Restrict delivery to specific countries (ISO codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_region_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific regions/states. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_metro_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific metro areas (DMA codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_postal_code_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific postal/ZIP codes. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ frequency_cap: Annotated[
+ Optional[FrequencyCap],
+ Field(
+ description="Frequency capping settings for package-level application",
+ title="Frequency Cap",
+ ),
+ ] = None
+
+
+class CreativeAssignment(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ creative_id: Annotated[str, Field(description="Unique identifier for the creative")]
+ weight: Annotated[
+ Optional[float],
+ Field(description="Delivery weight for this creative", ge=0.0, le=100.0),
+ ] = None
+
+
+class Status3(Enum):
+ draft = "draft"
+ active = "active"
+ paused = "paused"
+ completed = "completed"
+
+
+class Package(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ package_id: Annotated[
+ str, Field(description="Publisher's unique identifier for the package")
+ ]
+ buyer_ref: Annotated[
+ Optional[str],
+ Field(description="Buyer's reference identifier for this package"),
+ ] = None
+ product_id: Annotated[
+ Optional[str], Field(description="ID of the product this package is based on")
+ ] = None
+ budget: Annotated[
+ Optional[float],
+ Field(
+ description="Budget allocation for this package in the currency specified by the pricing option",
+ ge=0.0,
+ ),
+ ] = None
+ impressions: Annotated[
+ Optional[float], Field(description="Impression goal for this package", ge=0.0)
+ ] = None
+ targeting_overlay: Annotated[
+ Optional[TargetingOverlay],
+ Field(
+ description="Optional geographic refinements for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are primarily for geographic restrictions (RCT testing, regulatory compliance).",
+ title="Targeting Overlay",
+ ),
+ ] = None
+ creative_assignments: Annotated[
+ Optional[list[CreativeAssignment]],
+ Field(description="Creative assets assigned to this package"),
+ ] = None
+ formats_to_provide: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Format IDs that creative assets will be provided for this package"
+ ),
+ ] = None
+ status: Annotated[
+ Status3, Field(description="Status of a package", title="Package Status")
+ ]
+
+
+class MediaBuy(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ media_buy_id: Annotated[
+ str, Field(description="Publisher's unique identifier for the media buy")
+ ]
+ buyer_ref: Annotated[
+ Optional[str],
+ Field(description="Buyer's reference identifier for this media buy"),
+ ] = None
+ status: Annotated[
+ Status, Field(description="Status of a media buy", title="Media Buy Status")
+ ]
+ promoted_offering: Annotated[
+ str, Field(description="Description of advertiser and what is being promoted")
+ ]
+ total_budget: Annotated[float, Field(description="Total budget amount", ge=0.0)]
+ packages: Annotated[
+ list[Package], Field(description="Array of packages within this media buy")
+ ]
+ creative_deadline: Annotated[
+ Optional[AwareDatetime],
+ Field(description="ISO 8601 timestamp for creative upload deadline"),
+ ] = None
+ created_at: Annotated[
+ Optional[AwareDatetime], Field(description="Creation timestamp")
+ ] = None
+ updated_at: Annotated[
+ Optional[AwareDatetime], Field(description="Last update timestamp")
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_package_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_package_json.py
new file mode 100644
index 0000000..1da01a4
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_package_json.py
@@ -0,0 +1,123 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_package_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field, RootModel
+
+
+class GeoCountryAnyOfItem(RootModel[str]):
+ root: Annotated[str, Field(pattern="^[A-Z]{2}$")]
+
+
+class FrequencyCap(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ suppress_minutes: Annotated[
+ float, Field(description="Minutes to suppress after impression", ge=0.0)
+ ]
+
+
+class TargetingOverlay(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ geo_country_any_of: Annotated[
+ Optional[list[GeoCountryAnyOfItem]],
+ Field(
+ description="Restrict delivery to specific countries (ISO codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_region_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific regions/states. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_metro_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific metro areas (DMA codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_postal_code_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific postal/ZIP codes. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ frequency_cap: Annotated[
+ Optional[FrequencyCap],
+ Field(
+ description="Frequency capping settings for package-level application",
+ title="Frequency Cap",
+ ),
+ ] = None
+
+
+class CreativeAssignment(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ creative_id: Annotated[str, Field(description="Unique identifier for the creative")]
+ weight: Annotated[
+ Optional[float],
+ Field(description="Delivery weight for this creative", ge=0.0, le=100.0),
+ ] = None
+
+
+class Status(Enum):
+ draft = "draft"
+ active = "active"
+ paused = "paused"
+ completed = "completed"
+
+
+class Package(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ package_id: Annotated[
+ str, Field(description="Publisher's unique identifier for the package")
+ ]
+ buyer_ref: Annotated[
+ Optional[str],
+ Field(description="Buyer's reference identifier for this package"),
+ ] = None
+ product_id: Annotated[
+ Optional[str], Field(description="ID of the product this package is based on")
+ ] = None
+ budget: Annotated[
+ Optional[float],
+ Field(
+ description="Budget allocation for this package in the currency specified by the pricing option",
+ ge=0.0,
+ ),
+ ] = None
+ impressions: Annotated[
+ Optional[float], Field(description="Impression goal for this package", ge=0.0)
+ ] = None
+ targeting_overlay: Annotated[
+ Optional[TargetingOverlay],
+ Field(
+ description="Optional geographic refinements for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are primarily for geographic restrictions (RCT testing, regulatory compliance).",
+ title="Targeting Overlay",
+ ),
+ ] = None
+ creative_assignments: Annotated[
+ Optional[list[CreativeAssignment]],
+ Field(description="Creative assets assigned to this package"),
+ ] = None
+ formats_to_provide: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Format IDs that creative assets will be provided for this package"
+ ),
+ ] = None
+ status: Annotated[
+ Status, Field(description="Status of a package", title="Package Status")
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_pricing_option_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_pricing_option_json.py
new file mode 100644
index 0000000..4921d54
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_pricing_option_json.py
@@ -0,0 +1,404 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_pricing-option_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Literal, Optional, Union
+
+from pydantic import BaseModel, ConfigDict, Field, RootModel
+
+
+class PricingOption1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPM rate (cost per 1,000 impressions)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PriceGuidance(BaseModel):
+ floor: Annotated[
+ float,
+ Field(
+ description="Minimum bid price - publisher will reject bids under this value",
+ ge=0.0,
+ ),
+ ]
+ p25: Annotated[
+ Optional[float], Field(description="25th percentile winning price", ge=0.0)
+ ] = None
+ p50: Annotated[
+ Optional[float], Field(description="Median winning price", ge=0.0)
+ ] = None
+ p75: Annotated[
+ Optional[float], Field(description="75th percentile winning price", ge=0.0)
+ ] = None
+ p90: Annotated[
+ Optional[float], Field(description="90th percentile winning price", ge=0.0)
+ ] = None
+
+
+class PricingOption2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_auction')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ price_guidance: Annotated[
+ PriceGuidance,
+ Field(description="Pricing guidance for auction-based CPM bidding"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOption3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpc_usd_fixed')"
+ ),
+ ]
+ pricing_model: Annotated[Literal["cpc"], Field(description="Cost per click")]
+ rate: Annotated[float, Field(description="Fixed CPC rate (cost per click)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOption4(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpcv_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpcv"], Field(description="Cost per completed view (100% completion)")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPCV rate (cost per 100% completion)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class ViewThreshold(RootModel[float]):
+ root: Annotated[
+ float,
+ Field(
+ description="Percentage completion threshold for CPV pricing (0.0 to 1.0, e.g., 0.5 = 50% completion)",
+ ge=0.0,
+ le=1.0,
+ ),
+ ]
+
+
+class ViewThreshold1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_seconds: Annotated[
+ int,
+ Field(
+ description="Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view')",
+ ge=1,
+ ),
+ ]
+
+
+class Parameters(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ view_threshold: Union[ViewThreshold, ViewThreshold1]
+
+
+class PricingOption5(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpv_usd_50pct')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpv"], Field(description="Cost per view at threshold")
+ ]
+ rate: Annotated[float, Field(description="Fixed CPV rate (cost per view)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters,
+ Field(description="CPV-specific parameters defining the view threshold"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ demographic: Annotated[
+ str,
+ Field(
+ description="Target demographic in Nielsen format: P/M/W/A/C + age range. Examples: P18-49 (Persons 18-49), M25-54 (Men 25-54), W35+ (Women 35+), A18-34 (Adults 18-34), C2-11 (Children 2-11)",
+ pattern="^[PMWAC][0-9]{2}(-[0-9]{2}|\\+)$",
+ ),
+ ]
+ min_points: Annotated[
+ Optional[float],
+ Field(description="Minimum GRPs/TRPs required for this pricing option", ge=0.0),
+ ] = None
+
+
+class PricingOption6(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpp_usd_p18-49')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpp"], Field(description="Cost per Gross Rating Point")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPP rate (cost per rating point)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters1,
+ Field(
+ description="CPP-specific parameters for demographic targeting and GRP requirements"
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_hours: Annotated[
+ Optional[float],
+ Field(
+ description="Duration in hours for time-based flat rate pricing (DOOH)",
+ ge=0.0,
+ ),
+ ] = None
+ sov_percentage: Annotated[
+ Optional[float],
+ Field(
+ description="Guaranteed share of voice as percentage (DOOH, 0-100)",
+ ge=0.0,
+ le=100.0,
+ ),
+ ] = None
+ loop_duration_seconds: Annotated[
+ Optional[int],
+ Field(description="Duration of ad loop rotation in seconds (DOOH)", ge=1),
+ ] = None
+ min_plays_per_hour: Annotated[
+ Optional[int],
+ Field(
+ description="Minimum number of times ad plays per hour (DOOH frequency guarantee)",
+ ge=0,
+ ),
+ ] = None
+ venue_package: Annotated[
+ Optional[str],
+ Field(
+ description="Named venue package identifier for DOOH (e.g., 'times_square_network', 'airport_terminals')"
+ ),
+ ] = None
+ estimated_impressions: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated impressions for this flat rate option (informational, commonly used with SOV or time-based DOOH)",
+ ge=0,
+ ),
+ ] = None
+ daypart: Annotated[
+ Optional[str],
+ Field(
+ description="Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')"
+ ),
+ ] = None
+
+
+class PricingOption7(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'flat_rate_usd_24h_takeover')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["flat_rate"],
+ Field(description="Fixed cost regardless of delivery volume"),
+ ]
+ rate: Annotated[float, Field(description="Flat rate cost", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ is_fixed: Annotated[
+ Literal[True],
+ Field(
+ description="Whether this is a fixed rate (true) or auction-based (false)"
+ ),
+ ]
+ parameters: Annotated[
+ Optional[Parameters2],
+ Field(description="Flat rate parameters for DOOH and time-based campaigns"),
+ ] = None
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOption(
+ RootModel[
+ Union[
+ PricingOption1,
+ PricingOption2,
+ PricingOption3,
+ PricingOption4,
+ PricingOption5,
+ PricingOption6,
+ PricingOption7,
+ ]
+ ]
+):
+ root: Annotated[
+ Union[
+ PricingOption1,
+ PricingOption2,
+ PricingOption3,
+ PricingOption4,
+ PricingOption5,
+ PricingOption6,
+ PricingOption7,
+ ],
+ Field(
+ description="A pricing model option offered by a publisher for a product. Each pricing model has its own schema with model-specific requirements.",
+ title="Pricing Option",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_product_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_product_json.py
new file mode 100644
index 0000000..4f86e60
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_product_json.py
@@ -0,0 +1,1213 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_product_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Literal, Optional, Union
+
+from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, RootModel
+
+
+class PropertyType(Enum):
+ website = "website"
+ mobile_app = "mobile_app"
+ ctv_app = "ctv_app"
+ dooh = "dooh"
+ podcast = "podcast"
+ radio = "radio"
+ streaming_audio = "streaming_audio"
+
+
+class Identifier(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ type: Annotated[
+ str,
+ Field(
+ description="Type of identifier (e.g., 'domain', 'bundle_id', 'roku_store_id', 'podcast_guid')"
+ ),
+ ]
+ value: Annotated[
+ str,
+ Field(
+ description="The identifier value. For domain type: 'example.com' matches www.example.com and m.example.com only; 'subdomain.example.com' matches that specific subdomain; '*.example.com' matches all subdomains"
+ ),
+ ]
+
+
+class Tag(RootModel[str]):
+ root: Annotated[
+ str,
+ Field(
+ description="Lowercase tag with underscores (e.g., 'conde_nast_network', 'premium_content')",
+ pattern="^[a-z0-9_]+$",
+ ),
+ ]
+
+
+class Property(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ property_type: Annotated[
+ PropertyType, Field(description="Type of advertising property")
+ ]
+ name: Annotated[str, Field(description="Human-readable property name")]
+ identifiers: Annotated[
+ list[Identifier],
+ Field(description="Array of identifiers for this property", min_length=1),
+ ]
+ tags: Annotated[
+ Optional[list[Tag]],
+ Field(
+ description="Tags for categorization and grouping (e.g., network membership, content categories)"
+ ),
+ ] = None
+ publisher_domain: Annotated[
+ str,
+ Field(
+ description="Domain where adagents.json should be checked for authorization validation"
+ ),
+ ]
+
+
+class PropertyTag(RootModel[str]):
+ root: Annotated[
+ str,
+ Field(
+ description="Lowercase tag with underscores (e.g., 'local_radio', 'premium_content')",
+ pattern="^[a-z0-9_]+$",
+ ),
+ ]
+
+
+class DeliveryType(Enum):
+ guaranteed = "guaranteed"
+ non_guaranteed = "non_guaranteed"
+
+
+class PricingOptions(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPM rate (cost per 1,000 impressions)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PriceGuidance(BaseModel):
+ floor: Annotated[
+ float,
+ Field(
+ description="Minimum bid price - publisher will reject bids under this value",
+ ge=0.0,
+ ),
+ ]
+ p25: Annotated[
+ Optional[float], Field(description="25th percentile winning price", ge=0.0)
+ ] = None
+ p50: Annotated[
+ Optional[float], Field(description="Median winning price", ge=0.0)
+ ] = None
+ p75: Annotated[
+ Optional[float], Field(description="75th percentile winning price", ge=0.0)
+ ] = None
+ p90: Annotated[
+ Optional[float], Field(description="90th percentile winning price", ge=0.0)
+ ] = None
+
+
+class PricingOptions1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_auction')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ price_guidance: Annotated[
+ PriceGuidance,
+ Field(description="Pricing guidance for auction-based CPM bidding"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpc_usd_fixed')"
+ ),
+ ]
+ pricing_model: Annotated[Literal["cpc"], Field(description="Cost per click")]
+ rate: Annotated[float, Field(description="Fixed CPC rate (cost per click)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpcv_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpcv"], Field(description="Cost per completed view (100% completion)")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPCV rate (cost per 100% completion)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class ViewThreshold(RootModel[float]):
+ root: Annotated[
+ float,
+ Field(
+ description="Percentage completion threshold for CPV pricing (0.0 to 1.0, e.g., 0.5 = 50% completion)",
+ ge=0.0,
+ le=1.0,
+ ),
+ ]
+
+
+class ViewThreshold3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_seconds: Annotated[
+ int,
+ Field(
+ description="Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view')",
+ ge=1,
+ ),
+ ]
+
+
+class Parameters(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ view_threshold: Union[ViewThreshold, ViewThreshold3]
+
+
+class PricingOptions4(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpv_usd_50pct')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpv"], Field(description="Cost per view at threshold")
+ ]
+ rate: Annotated[float, Field(description="Fixed CPV rate (cost per view)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters,
+ Field(description="CPV-specific parameters defining the view threshold"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters4(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ demographic: Annotated[
+ str,
+ Field(
+ description="Target demographic in Nielsen format: P/M/W/A/C + age range. Examples: P18-49 (Persons 18-49), M25-54 (Men 25-54), W35+ (Women 35+), A18-34 (Adults 18-34), C2-11 (Children 2-11)",
+ pattern="^[PMWAC][0-9]{2}(-[0-9]{2}|\\+)$",
+ ),
+ ]
+ min_points: Annotated[
+ Optional[float],
+ Field(description="Minimum GRPs/TRPs required for this pricing option", ge=0.0),
+ ] = None
+
+
+class PricingOptions5(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpp_usd_p18-49')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpp"], Field(description="Cost per Gross Rating Point")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPP rate (cost per rating point)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters4,
+ Field(
+ description="CPP-specific parameters for demographic targeting and GRP requirements"
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters5(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_hours: Annotated[
+ Optional[float],
+ Field(
+ description="Duration in hours for time-based flat rate pricing (DOOH)",
+ ge=0.0,
+ ),
+ ] = None
+ sov_percentage: Annotated[
+ Optional[float],
+ Field(
+ description="Guaranteed share of voice as percentage (DOOH, 0-100)",
+ ge=0.0,
+ le=100.0,
+ ),
+ ] = None
+ loop_duration_seconds: Annotated[
+ Optional[int],
+ Field(description="Duration of ad loop rotation in seconds (DOOH)", ge=1),
+ ] = None
+ min_plays_per_hour: Annotated[
+ Optional[int],
+ Field(
+ description="Minimum number of times ad plays per hour (DOOH frequency guarantee)",
+ ge=0,
+ ),
+ ] = None
+ venue_package: Annotated[
+ Optional[str],
+ Field(
+ description="Named venue package identifier for DOOH (e.g., 'times_square_network', 'airport_terminals')"
+ ),
+ ] = None
+ estimated_impressions: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated impressions for this flat rate option (informational, commonly used with SOV or time-based DOOH)",
+ ge=0,
+ ),
+ ] = None
+ daypart: Annotated[
+ Optional[str],
+ Field(
+ description="Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')"
+ ),
+ ] = None
+
+
+class PricingOptions6(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'flat_rate_usd_24h_takeover')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["flat_rate"],
+ Field(description="Fixed cost regardless of delivery volume"),
+ ]
+ rate: Annotated[float, Field(description="Flat rate cost", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ is_fixed: Annotated[
+ Literal[True],
+ Field(
+ description="Whether this is a fixed rate (true) or auction-based (false)"
+ ),
+ ]
+ parameters: Annotated[
+ Optional[Parameters5],
+ Field(description="Flat rate parameters for DOOH and time-based campaigns"),
+ ] = None
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Measurement(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ type: Annotated[
+ str,
+ Field(
+ description="Type of measurement",
+ examples=["incremental_sales_lift", "brand_lift", "foot_traffic"],
+ ),
+ ]
+ attribution: Annotated[
+ str,
+ Field(
+ description="Attribution methodology",
+ examples=["deterministic_purchase", "probabilistic"],
+ ),
+ ]
+ window: Annotated[
+ Optional[str],
+ Field(description="Attribution window", examples=["30_days", "7_days"]),
+ ] = None
+ reporting: Annotated[
+ str,
+ Field(
+ description="Reporting frequency and format",
+ examples=["weekly_dashboard", "real_time_api"],
+ ),
+ ]
+
+
+class DeliveryMeasurement(BaseModel):
+ provider: Annotated[
+ str,
+ Field(
+ description="Measurement provider(s) used for this product (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions')"
+ ),
+ ]
+ notes: Annotated[
+ Optional[str],
+ Field(
+ description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly')"
+ ),
+ ] = None
+
+
+class AvailableReportingFrequency(Enum):
+ hourly = "hourly"
+ daily = "daily"
+ monthly = "monthly"
+
+
+class AvailableMetric(Enum):
+ impressions = "impressions"
+ spend = "spend"
+ clicks = "clicks"
+ ctr = "ctr"
+ video_completions = "video_completions"
+ completion_rate = "completion_rate"
+ conversions = "conversions"
+ viewability = "viewability"
+ engagement_rate = "engagement_rate"
+
+
+class ReportingCapabilities(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ available_reporting_frequencies: Annotated[
+ list[AvailableReportingFrequency],
+ Field(description="Supported reporting frequency options", min_length=1),
+ ]
+ expected_delay_minutes: Annotated[
+ int,
+ Field(
+ description="Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)",
+ examples=[240, 300, 1440],
+ ge=0,
+ ),
+ ]
+ timezone: Annotated[
+ str,
+ Field(
+ description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.",
+ examples=[
+ "UTC",
+ "America/New_York",
+ "Europe/London",
+ "America/Los_Angeles",
+ ],
+ ),
+ ]
+ supports_webhooks: Annotated[
+ bool,
+ Field(
+ description="Whether this product supports webhook-based reporting notifications"
+ ),
+ ]
+ available_metrics: Annotated[
+ list[AvailableMetric],
+ Field(
+ description="Metrics available in reporting. Impressions and spend are always implicitly included.",
+ examples=[
+ ["impressions", "spend", "clicks", "video_completions"],
+ ["impressions", "spend", "conversions"],
+ ],
+ ),
+ ]
+
+
+class CoBranding(Enum):
+ required = "required"
+ optional = "optional"
+ none = "none"
+
+
+class LandingPage(Enum):
+ any = "any"
+ retailer_site_only = "retailer_site_only"
+ must_include_retailer = "must_include_retailer"
+
+
+class CreativePolicy(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ co_branding: Annotated[CoBranding, Field(description="Co-branding requirement")]
+ landing_page: Annotated[LandingPage, Field(description="Landing page requirements")]
+ templates_available: Annotated[
+ bool, Field(description="Whether creative templates are provided")
+ ]
+
+
+class Product1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ product_id: Annotated[str, Field(description="Unique identifier for the product")]
+ name: Annotated[str, Field(description="Human-readable product name")]
+ description: Annotated[
+ str, Field(description="Detailed description of the product and its inventory")
+ ]
+ properties: Annotated[
+ list[Property],
+ Field(
+ description="Array of advertising properties covered by this product for adagents.json validation",
+ min_length=1,
+ ),
+ ]
+ property_tags: Annotated[
+ Optional[list[PropertyTag]],
+ Field(
+ description="Tags identifying groups of properties covered by this product (use list_authorized_properties to get full property details)",
+ min_length=1,
+ ),
+ ] = None
+ format_ids: Annotated[
+ list[str],
+ Field(
+ description="Array of supported creative format IDs - use list_creative_formats to get full format details"
+ ),
+ ]
+ delivery_type: Annotated[
+ DeliveryType,
+ Field(description="Type of inventory delivery", title="Delivery Type"),
+ ]
+ pricing_options: Annotated[
+ list[
+ Union[
+ PricingOptions,
+ PricingOptions1,
+ PricingOptions2,
+ PricingOptions3,
+ PricingOptions4,
+ PricingOptions5,
+ PricingOptions6,
+ ]
+ ],
+ Field(description="Available pricing models for this product", min_length=1),
+ ]
+ estimated_exposures: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated exposures/impressions for guaranteed products", ge=0
+ ),
+ ] = None
+ measurement: Annotated[
+ Optional[Measurement],
+ Field(
+ description="Measurement capabilities included with a product",
+ title="Measurement",
+ ),
+ ] = None
+ delivery_measurement: Annotated[
+ DeliveryMeasurement,
+ Field(
+ description="Measurement provider and methodology for delivery metrics. The buyer accepts the declared provider as the source of truth for the buy. REQUIRED for all products."
+ ),
+ ]
+ reporting_capabilities: Annotated[
+ Optional[ReportingCapabilities],
+ Field(
+ description="Reporting capabilities available for a product",
+ title="Reporting Capabilities",
+ ),
+ ] = None
+ creative_policy: Annotated[
+ Optional[CreativePolicy],
+ Field(
+ description="Creative requirements and restrictions for a product",
+ title="Creative Policy",
+ ),
+ ] = None
+ is_custom: Annotated[
+ Optional[bool], Field(description="Whether this is a custom product")
+ ] = None
+ brief_relevance: Annotated[
+ Optional[str],
+ Field(
+ description="Explanation of why this product matches the brief (only included when brief is provided)"
+ ),
+ ] = None
+ expires_at: Annotated[
+ Optional[AwareDatetime],
+ Field(description="Expiration timestamp for custom products"),
+ ] = None
+
+
+class Property1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ property_type: Annotated[
+ PropertyType, Field(description="Type of advertising property")
+ ]
+ name: Annotated[str, Field(description="Human-readable property name")]
+ identifiers: Annotated[
+ list[Identifier],
+ Field(description="Array of identifiers for this property", min_length=1),
+ ]
+ tags: Annotated[
+ Optional[list[Tag]],
+ Field(
+ description="Tags for categorization and grouping (e.g., network membership, content categories)"
+ ),
+ ] = None
+ publisher_domain: Annotated[
+ str,
+ Field(
+ description="Domain where adagents.json should be checked for authorization validation"
+ ),
+ ]
+
+
+class PricingOptions7(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPM rate (cost per 1,000 impressions)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions8(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_auction')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ price_guidance: Annotated[
+ PriceGuidance,
+ Field(description="Pricing guidance for auction-based CPM bidding"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions9(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpc_usd_fixed')"
+ ),
+ ]
+ pricing_model: Annotated[Literal["cpc"], Field(description="Cost per click")]
+ rate: Annotated[float, Field(description="Fixed CPC rate (cost per click)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions10(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpcv_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpcv"], Field(description="Cost per completed view (100% completion)")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPCV rate (cost per 100% completion)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class ViewThreshold4(RootModel[float]):
+ root: Annotated[
+ float,
+ Field(
+ description="Percentage completion threshold for CPV pricing (0.0 to 1.0, e.g., 0.5 = 50% completion)",
+ ge=0.0,
+ le=1.0,
+ ),
+ ]
+
+
+class ViewThreshold5(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_seconds: Annotated[
+ int,
+ Field(
+ description="Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view')",
+ ge=1,
+ ),
+ ]
+
+
+class Parameters6(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ view_threshold: Union[ViewThreshold4, ViewThreshold5]
+
+
+class PricingOptions11(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpv_usd_50pct')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpv"], Field(description="Cost per view at threshold")
+ ]
+ rate: Annotated[float, Field(description="Fixed CPV rate (cost per view)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters6,
+ Field(description="CPV-specific parameters defining the view threshold"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters7(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ demographic: Annotated[
+ str,
+ Field(
+ description="Target demographic in Nielsen format: P/M/W/A/C + age range. Examples: P18-49 (Persons 18-49), M25-54 (Men 25-54), W35+ (Women 35+), A18-34 (Adults 18-34), C2-11 (Children 2-11)",
+ pattern="^[PMWAC][0-9]{2}(-[0-9]{2}|\\+)$",
+ ),
+ ]
+ min_points: Annotated[
+ Optional[float],
+ Field(description="Minimum GRPs/TRPs required for this pricing option", ge=0.0),
+ ] = None
+
+
+class PricingOptions12(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpp_usd_p18-49')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpp"], Field(description="Cost per Gross Rating Point")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPP rate (cost per rating point)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters7,
+ Field(
+ description="CPP-specific parameters for demographic targeting and GRP requirements"
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters8(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_hours: Annotated[
+ Optional[float],
+ Field(
+ description="Duration in hours for time-based flat rate pricing (DOOH)",
+ ge=0.0,
+ ),
+ ] = None
+ sov_percentage: Annotated[
+ Optional[float],
+ Field(
+ description="Guaranteed share of voice as percentage (DOOH, 0-100)",
+ ge=0.0,
+ le=100.0,
+ ),
+ ] = None
+ loop_duration_seconds: Annotated[
+ Optional[int],
+ Field(description="Duration of ad loop rotation in seconds (DOOH)", ge=1),
+ ] = None
+ min_plays_per_hour: Annotated[
+ Optional[int],
+ Field(
+ description="Minimum number of times ad plays per hour (DOOH frequency guarantee)",
+ ge=0,
+ ),
+ ] = None
+ venue_package: Annotated[
+ Optional[str],
+ Field(
+ description="Named venue package identifier for DOOH (e.g., 'times_square_network', 'airport_terminals')"
+ ),
+ ] = None
+ estimated_impressions: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated impressions for this flat rate option (informational, commonly used with SOV or time-based DOOH)",
+ ge=0,
+ ),
+ ] = None
+ daypart: Annotated[
+ Optional[str],
+ Field(
+ description="Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')"
+ ),
+ ] = None
+
+
+class PricingOptions13(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'flat_rate_usd_24h_takeover')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["flat_rate"],
+ Field(description="Fixed cost regardless of delivery volume"),
+ ]
+ rate: Annotated[float, Field(description="Flat rate cost", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ is_fixed: Annotated[
+ Literal[True],
+ Field(
+ description="Whether this is a fixed rate (true) or auction-based (false)"
+ ),
+ ]
+ parameters: Annotated[
+ Optional[Parameters8],
+ Field(description="Flat rate parameters for DOOH and time-based campaigns"),
+ ] = None
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class ReportingCapabilities1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ available_reporting_frequencies: Annotated[
+ list[AvailableReportingFrequency],
+ Field(description="Supported reporting frequency options", min_length=1),
+ ]
+ expected_delay_minutes: Annotated[
+ int,
+ Field(
+ description="Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)",
+ examples=[240, 300, 1440],
+ ge=0,
+ ),
+ ]
+ timezone: Annotated[
+ str,
+ Field(
+ description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.",
+ examples=[
+ "UTC",
+ "America/New_York",
+ "Europe/London",
+ "America/Los_Angeles",
+ ],
+ ),
+ ]
+ supports_webhooks: Annotated[
+ bool,
+ Field(
+ description="Whether this product supports webhook-based reporting notifications"
+ ),
+ ]
+ available_metrics: Annotated[
+ list[AvailableMetric],
+ Field(
+ description="Metrics available in reporting. Impressions and spend are always implicitly included.",
+ examples=[
+ ["impressions", "spend", "clicks", "video_completions"],
+ ["impressions", "spend", "conversions"],
+ ],
+ ),
+ ]
+
+
+class CreativePolicy2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ co_branding: Annotated[CoBranding, Field(description="Co-branding requirement")]
+ landing_page: Annotated[LandingPage, Field(description="Landing page requirements")]
+ templates_available: Annotated[
+ bool, Field(description="Whether creative templates are provided")
+ ]
+
+
+class Product2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ product_id: Annotated[str, Field(description="Unique identifier for the product")]
+ name: Annotated[str, Field(description="Human-readable product name")]
+ description: Annotated[
+ str, Field(description="Detailed description of the product and its inventory")
+ ]
+ properties: Annotated[
+ Optional[list[Property1]],
+ Field(
+ description="Array of advertising properties covered by this product for adagents.json validation",
+ min_length=1,
+ ),
+ ] = None
+ property_tags: Annotated[
+ list[PropertyTag],
+ Field(
+ description="Tags identifying groups of properties covered by this product (use list_authorized_properties to get full property details)",
+ min_length=1,
+ ),
+ ]
+ format_ids: Annotated[
+ list[str],
+ Field(
+ description="Array of supported creative format IDs - use list_creative_formats to get full format details"
+ ),
+ ]
+ delivery_type: Annotated[
+ DeliveryType,
+ Field(description="Type of inventory delivery", title="Delivery Type"),
+ ]
+ pricing_options: Annotated[
+ list[
+ Union[
+ PricingOptions7,
+ PricingOptions8,
+ PricingOptions9,
+ PricingOptions10,
+ PricingOptions11,
+ PricingOptions12,
+ PricingOptions13,
+ ]
+ ],
+ Field(description="Available pricing models for this product", min_length=1),
+ ]
+ estimated_exposures: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated exposures/impressions for guaranteed products", ge=0
+ ),
+ ] = None
+ measurement: Annotated[
+ Optional[Measurement],
+ Field(
+ description="Measurement capabilities included with a product",
+ title="Measurement",
+ ),
+ ] = None
+ delivery_measurement: Annotated[
+ DeliveryMeasurement,
+ Field(
+ description="Measurement provider and methodology for delivery metrics. The buyer accepts the declared provider as the source of truth for the buy. REQUIRED for all products."
+ ),
+ ]
+ reporting_capabilities: Annotated[
+ Optional[ReportingCapabilities1],
+ Field(
+ description="Reporting capabilities available for a product",
+ title="Reporting Capabilities",
+ ),
+ ] = None
+ creative_policy: Annotated[
+ Optional[CreativePolicy2],
+ Field(
+ description="Creative requirements and restrictions for a product",
+ title="Creative Policy",
+ ),
+ ] = None
+ is_custom: Annotated[
+ Optional[bool], Field(description="Whether this is a custom product")
+ ] = None
+ brief_relevance: Annotated[
+ Optional[str],
+ Field(
+ description="Explanation of why this product matches the brief (only included when brief is provided)"
+ ),
+ ] = None
+ expires_at: Annotated[
+ Optional[AwareDatetime],
+ Field(description="Expiration timestamp for custom products"),
+ ] = None
+
+
+class Product(RootModel[Union[Product1, Product2]]):
+ root: Annotated[
+ Union[Product1, Product2],
+ Field(
+ description="Represents available advertising inventory", title="Product"
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_property_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_property_json.py
new file mode 100644
index 0000000..7eb2d61
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_property_json.py
@@ -0,0 +1,73 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_property_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field, RootModel
+
+
+class PropertyType(Enum):
+ website = "website"
+ mobile_app = "mobile_app"
+ ctv_app = "ctv_app"
+ dooh = "dooh"
+ podcast = "podcast"
+ radio = "radio"
+ streaming_audio = "streaming_audio"
+
+
+class Identifier(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ type: Annotated[
+ str,
+ Field(
+ description="Type of identifier (e.g., 'domain', 'bundle_id', 'roku_store_id', 'podcast_guid')"
+ ),
+ ]
+ value: Annotated[
+ str,
+ Field(
+ description="The identifier value. For domain type: 'example.com' matches www.example.com and m.example.com only; 'subdomain.example.com' matches that specific subdomain; '*.example.com' matches all subdomains"
+ ),
+ ]
+
+
+class Tag(RootModel[str]):
+ root: Annotated[
+ str,
+ Field(
+ description="Lowercase tag with underscores (e.g., 'conde_nast_network', 'premium_content')",
+ pattern="^[a-z0-9_]+$",
+ ),
+ ]
+
+
+class Property(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ property_type: Annotated[
+ PropertyType, Field(description="Type of advertising property")
+ ]
+ name: Annotated[str, Field(description="Human-readable property name")]
+ identifiers: Annotated[
+ list[Identifier],
+ Field(description="Array of identifiers for this property", min_length=1),
+ ]
+ tags: Annotated[
+ Optional[list[Tag]],
+ Field(
+ description="Tags for categorization and grouping (e.g., network membership, content categories)"
+ ),
+ ] = None
+ publisher_domain: Annotated[
+ str,
+ Field(
+ description="Domain where adagents.json should be checked for authorization validation"
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_push_notification_config_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_push_notification_config_json.py
new file mode 100644
index 0000000..99fbd85
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_push_notification_config_json.py
@@ -0,0 +1,57 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_push-notification-config_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class Scheme(Enum):
+ bearer = "Bearer"
+ hmac_sha256 = "HMAC-SHA256"
+
+
+class Authentication(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ schemes: Annotated[
+ list[Scheme],
+ Field(
+ description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for signature verification (recommended for production)",
+ max_length=1,
+ min_length=1,
+ ),
+ ]
+ credentials: Annotated[
+ str,
+ Field(
+ description="Credentials for authentication. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.",
+ min_length=32,
+ ),
+ ]
+
+
+class PushNotificationConfig(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ AnyUrl, Field(description="Webhook endpoint URL for task status notifications")
+ ]
+ token: Annotated[
+ Optional[str],
+ Field(
+ description="Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.",
+ min_length=16,
+ ),
+ ] = None
+ authentication: Annotated[
+ Authentication,
+ Field(
+ description="Authentication configuration for webhook delivery (A2A-compatible)"
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_reporting_capabilities_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_reporting_capabilities_json.py
new file mode 100644
index 0000000..19367e6
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_reporting_capabilities_json.py
@@ -0,0 +1,73 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_reporting-capabilities_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class AvailableReportingFrequency(Enum):
+ hourly = "hourly"
+ daily = "daily"
+ monthly = "monthly"
+
+
+class AvailableMetric(Enum):
+ impressions = "impressions"
+ spend = "spend"
+ clicks = "clicks"
+ ctr = "ctr"
+ video_completions = "video_completions"
+ completion_rate = "completion_rate"
+ conversions = "conversions"
+ viewability = "viewability"
+ engagement_rate = "engagement_rate"
+
+
+class ReportingCapabilities(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ available_reporting_frequencies: Annotated[
+ list[AvailableReportingFrequency],
+ Field(description="Supported reporting frequency options", min_length=1),
+ ]
+ expected_delay_minutes: Annotated[
+ int,
+ Field(
+ description="Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)",
+ examples=[240, 300, 1440],
+ ge=0,
+ ),
+ ]
+ timezone: Annotated[
+ str,
+ Field(
+ description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.",
+ examples=[
+ "UTC",
+ "America/New_York",
+ "Europe/London",
+ "America/Los_Angeles",
+ ],
+ ),
+ ]
+ supports_webhooks: Annotated[
+ bool,
+ Field(
+ description="Whether this product supports webhook-based reporting notifications"
+ ),
+ ]
+ available_metrics: Annotated[
+ list[AvailableMetric],
+ Field(
+ description="Metrics available in reporting. Impressions and spend are always implicitly included.",
+ examples=[
+ ["impressions", "spend", "clicks", "video_completions"],
+ ["impressions", "spend", "conversions"],
+ ],
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_response_json.py
new file mode 100644
index 0000000..1a95061
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_response_json.py
@@ -0,0 +1,44 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_response_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class Status(Enum):
+ submitted = "submitted"
+ working = "working"
+ input_required = "input-required"
+ completed = "completed"
+ canceled = "canceled"
+ failed = "failed"
+ rejected = "rejected"
+ auth_required = "auth-required"
+ unknown = "unknown"
+
+
+class ProtocolResponse(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ message: Annotated[str, Field(description="Human-readable summary")]
+ status: Annotated[
+ Optional[Status],
+ Field(
+ description="Standardized task status values based on A2A TaskState enum. Indicates the current state of any AdCP operation.",
+ title="Task Status",
+ ),
+ ] = None
+ context_id: Annotated[
+ Optional[str], Field(description="Session continuity identifier")
+ ] = None
+ data: Annotated[
+ Optional[Any],
+ Field(
+ description="AdCP task-specific response data (see individual task response schemas)"
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_start_timing_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_start_timing_json.py
new file mode 100644
index 0000000..3d8e36d
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_start_timing_json.py
@@ -0,0 +1,18 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_start-timing_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Union
+
+from pydantic import AwareDatetime, Field, RootModel
+
+
+class StartTiming(RootModel[Union[str, AwareDatetime]]):
+ root: Annotated[
+ Union[str, AwareDatetime],
+ Field(
+ description="Campaign start timing: 'asap' or ISO 8601 date-time",
+ title="Start Timing",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_sub_asset_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_sub_asset_json.py
new file mode 100644
index 0000000..e9f3b32
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_sub_asset_json.py
@@ -0,0 +1,67 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_sub-asset_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Optional, Union
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel
+
+
+class SubAsset1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Annotated[
+ str,
+ Field(
+ description="Type of asset. Common types: headline, body_text, thumbnail_image, product_image, featured_image, logo, cta_text, price_text, sponsor_name, author_name, click_url"
+ ),
+ ]
+ asset_id: Annotated[
+ str, Field(description="Unique identifier for the asset within the creative")
+ ]
+ content_uri: Annotated[
+ AnyUrl, Field(description="URL for media assets (images, videos, etc.)")
+ ]
+ content: Annotated[
+ Optional[Union[str, list[str]]],
+ Field(
+ description="Text content for text-based assets like headlines, body text, CTA text, etc."
+ ),
+ ] = None
+
+
+class SubAsset2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Annotated[
+ str,
+ Field(
+ description="Type of asset. Common types: headline, body_text, thumbnail_image, product_image, featured_image, logo, cta_text, price_text, sponsor_name, author_name, click_url"
+ ),
+ ]
+ asset_id: Annotated[
+ str, Field(description="Unique identifier for the asset within the creative")
+ ]
+ content_uri: Annotated[
+ Optional[AnyUrl],
+ Field(description="URL for media assets (images, videos, etc.)"),
+ ] = None
+ content: Annotated[
+ Union[str, list[str]],
+ Field(
+ description="Text content for text-based assets like headlines, body text, CTA text, etc."
+ ),
+ ]
+
+
+class SubAsset(RootModel[Union[SubAsset1, SubAsset2]]):
+ root: Annotated[
+ Union[SubAsset1, SubAsset2],
+ Field(
+ description="Sub-asset for multi-asset creative formats, including carousel images and native ad template variables",
+ title="Sub-Asset",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_core_targeting_json.py b/src/creative_agent/schemas_generated/_schemas_v1_core_targeting_json.py
new file mode 100644
index 0000000..325f1c4
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_core_targeting_json.py
@@ -0,0 +1,58 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_core_targeting_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field, RootModel
+
+
+class GeoCountryAnyOfItem(RootModel[str]):
+ root: Annotated[str, Field(pattern="^[A-Z]{2}$")]
+
+
+class FrequencyCap(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ suppress_minutes: Annotated[
+ float, Field(description="Minutes to suppress after impression", ge=0.0)
+ ]
+
+
+class TargetingOverlay(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ geo_country_any_of: Annotated[
+ Optional[list[GeoCountryAnyOfItem]],
+ Field(
+ description="Restrict delivery to specific countries (ISO codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_region_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific regions/states. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_metro_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific metro areas (DMA codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_postal_code_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific postal/ZIP codes. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ frequency_cap: Annotated[
+ Optional[FrequencyCap],
+ Field(
+ description="Frequency capping settings for package-level application",
+ title="Frequency Cap",
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_channels_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_channels_json.py
new file mode 100644
index 0000000..3e7930d
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_channels_json.py
@@ -0,0 +1,18 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_channels_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class AdvertisingChannels(Enum):
+ display = "display"
+ video = "video"
+ audio = "audio"
+ native = "native"
+ dooh = "dooh"
+ ctv = "ctv"
+ podcast = "podcast"
+ retail = "retail"
+ social = "social"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_creative_status_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_creative_status_json.py
new file mode 100644
index 0000000..58175d2
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_creative_status_json.py
@@ -0,0 +1,13 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_creative-status_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class CreativeStatus(Enum):
+ processing = "processing"
+ approved = "approved"
+ rejected = "rejected"
+ pending_review = "pending_review"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_delivery_type_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_delivery_type_json.py
new file mode 100644
index 0000000..d1ea13c
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_delivery_type_json.py
@@ -0,0 +1,11 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_delivery-type_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class DeliveryType(Enum):
+ guaranteed = "guaranteed"
+ non_guaranteed = "non_guaranteed"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_frequency_cap_scope_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_frequency_cap_scope_json.py
new file mode 100644
index 0000000..aa4dc0d
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_frequency_cap_scope_json.py
@@ -0,0 +1,10 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_frequency-cap-scope_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class FrequencyCapScope(Enum):
+ package = "package"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_media_buy_status_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_media_buy_status_json.py
new file mode 100644
index 0000000..ef90b23
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_media_buy_status_json.py
@@ -0,0 +1,13 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_media-buy-status_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class MediaBuyStatus(Enum):
+ pending_activation = "pending_activation"
+ active = "active"
+ paused = "paused"
+ completed = "completed"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_pacing_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_pacing_json.py
new file mode 100644
index 0000000..ad4df29
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_pacing_json.py
@@ -0,0 +1,12 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_pacing_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class Pacing(Enum):
+ even = "even"
+ asap = "asap"
+ front_loaded = "front_loaded"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_package_status_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_package_status_json.py
new file mode 100644
index 0000000..89726e0
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_package_status_json.py
@@ -0,0 +1,13 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_package-status_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class PackageStatus(Enum):
+ draft = "draft"
+ active = "active"
+ paused = "paused"
+ completed = "completed"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_pricing_model_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_pricing_model_json.py
new file mode 100644
index 0000000..930a0ad
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_pricing_model_json.py
@@ -0,0 +1,15 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_pricing-model_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class PricingModel(Enum):
+ cpm = "cpm"
+ cpc = "cpc"
+ cpcv = "cpcv"
+ cpv = "cpv"
+ cpp = "cpp"
+ flat_rate = "flat_rate"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_snippet_type_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_snippet_type_json.py
new file mode 100644
index 0000000..72be5f6
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_snippet_type_json.py
@@ -0,0 +1,15 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_snippet-type_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class SnippetType(Enum):
+ vast_xml = "vast_xml"
+ vast_url = "vast_url"
+ html = "html"
+ javascript = "javascript"
+ iframe = "iframe"
+ daast_url = "daast_url"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_standard_format_ids_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_standard_format_ids_json.py
new file mode 100644
index 0000000..629e865
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_standard_format_ids_json.py
@@ -0,0 +1,44 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_standard-format-ids_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class StandardFormatIds(Enum):
+ display_300x250 = "display_300x250"
+ display_728x90 = "display_728x90"
+ display_320x50 = "display_320x50"
+ display_160x600 = "display_160x600"
+ display_970x250 = "display_970x250"
+ display_336x280 = "display_336x280"
+ display_expandable_300x250 = "display_expandable_300x250"
+ display_expandable_728x90 = "display_expandable_728x90"
+ display_interstitial_320x480 = "display_interstitial_320x480"
+ display_interstitial_desktop = "display_interstitial_desktop"
+ display_dynamic_300x250 = "display_dynamic_300x250"
+ display_responsive = "display_responsive"
+ native_in_feed = "native_in_feed"
+ native_content_recommendation = "native_content_recommendation"
+ native_product = "native_product"
+ video_skippable_15s = "video_skippable_15s"
+ video_skippable_30s = "video_skippable_30s"
+ video_non_skippable_15s = "video_non_skippable_15s"
+ video_non_skippable_30s = "video_non_skippable_30s"
+ video_outstream_autoplay = "video_outstream_autoplay"
+ video_vertical_story = "video_vertical_story"
+ video_rewarded_30s = "video_rewarded_30s"
+ video_pause_ad = "video_pause_ad"
+ video_ctv_non_skippable_30s = "video_ctv_non_skippable_30s"
+ audio_standard_15s = "audio_standard_15s"
+ audio_standard_30s = "audio_standard_30s"
+ audio_podcast_host_read = "audio_podcast_host_read"
+ audio_programmatic = "audio_programmatic"
+ universal_carousel = "universal_carousel"
+ universal_canvas = "universal_canvas"
+ universal_takeover = "universal_takeover"
+ universal_gallery = "universal_gallery"
+ universal_reveal = "universal_reveal"
+ dooh_landscape_static = "dooh_landscape_static"
+ dooh_portrait_video = "dooh_portrait_video"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_enums_task_status_json.py b/src/creative_agent/schemas_generated/_schemas_v1_enums_task_status_json.py
new file mode 100644
index 0000000..36b2c6f
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_enums_task_status_json.py
@@ -0,0 +1,18 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_enums_task-status_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+
+
+class TaskStatus(Enum):
+ submitted = "submitted"
+ working = "working"
+ input_required = "input-required"
+ completed = "completed"
+ canceled = "canceled"
+ failed = "failed"
+ rejected = "rejected"
+ auth_required = "auth-required"
+ unknown = "unknown"
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_add_creative_assets_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_add_creative_assets_request_json.py
new file mode 100644
index 0000000..ca80970
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_add_creative_assets_request_json.py
@@ -0,0 +1,468 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_add-creative-assets-request_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Literal, Optional, Union
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field, RootModel
+
+
+class FormatId(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ agent_url: Annotated[
+ AnyUrl,
+ Field(
+ 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: Annotated[
+ str,
+ Field(
+ description="Format identifier within the agent's namespace (e.g., 'display_300x250', 'video_standard_30s')",
+ pattern="^[a-zA-Z0-9_-]+$",
+ ),
+ ]
+
+
+class Assets(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["image"]
+ url: Annotated[AnyUrl, Field(description="URL to the image asset")]
+ width: Annotated[
+ Optional[int], Field(description="Image width in pixels", ge=1)
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image height in pixels", ge=1)
+ ] = None
+ format: Annotated[
+ Optional[str],
+ Field(description="Image file format (jpg, png, gif, webp, etc.)"),
+ ] = None
+ alt_text: Annotated[
+ Optional[str], Field(description="Alternative text for accessibility")
+ ] = None
+
+
+class Assets12(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["video"]
+ url: Annotated[AnyUrl, Field(description="URL to the video asset")]
+ width: Annotated[
+ Optional[int], Field(description="Video width in pixels", ge=1)
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Video height in pixels", ge=1)
+ ] = None
+ duration_ms: Annotated[
+ Optional[int], Field(description="Video duration in milliseconds", ge=0)
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="Video file format (mp4, webm, mov, etc.)")
+ ] = None
+ bitrate_kbps: Annotated[
+ Optional[int], Field(description="Video bitrate in kilobits per second", ge=1)
+ ] = None
+
+
+class Assets13(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["audio"]
+ url: Annotated[AnyUrl, Field(description="URL to the audio asset")]
+ duration_ms: Annotated[
+ Optional[int], Field(description="Audio duration in milliseconds", ge=0)
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="Audio file format (mp3, wav, aac, etc.)")
+ ] = None
+ bitrate_kbps: Annotated[
+ Optional[int], Field(description="Audio bitrate in kilobits per second", ge=1)
+ ] = None
+
+
+class Assets14(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["text"]
+ content: Annotated[str, Field(description="Text content")]
+ max_length: Annotated[
+ Optional[int], Field(description="Maximum character length constraint", ge=1)
+ ] = None
+ language: Annotated[
+ Optional[str], Field(description="Language code (e.g., 'en', 'es', 'fr')")
+ ] = None
+
+
+class Assets15(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["html"]
+ content: Annotated[str, Field(description="HTML content")]
+ version: Annotated[
+ Optional[str], Field(description="HTML version (e.g., 'HTML5')")
+ ] = None
+
+
+class Assets16(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["css"]
+ content: Annotated[str, Field(description="CSS content")]
+ media: Annotated[
+ Optional[str],
+ Field(description="CSS media query context (e.g., 'screen', 'print')"),
+ ] = None
+
+
+class ModuleType(Enum):
+ esm = "esm"
+ commonjs = "commonjs"
+ script = "script"
+
+
+class Assets17(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["javascript"]
+ content: Annotated[str, Field(description="JavaScript content")]
+ module_type: Annotated[
+ Optional[ModuleType], Field(description="JavaScript module type")
+ ] = None
+
+
+class Colors(BaseModel):
+ primary: Optional[str] = None
+ secondary: Optional[str] = None
+ accent: Optional[str] = None
+
+
+class Assets18(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["promoted_offerings"]
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="URL of the advertiser's brand or offering (e.g., https://retailer.com)"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand colors")] = None
+ fonts: Annotated[Optional[list[str]], Field(description="Brand fonts")] = None
+ tone: Annotated[Optional[str], Field(description="Brand tone/voice")] = None
+
+
+class Assets19(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["url"]
+ url: Annotated[AnyUrl, Field(description="URL reference")]
+ description: Annotated[
+ Optional[str], Field(description="Description of what this URL points to")
+ ] = None
+
+
+class Input(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ name: Annotated[
+ str, Field(description="Human-readable name for this preview variant")
+ ]
+ macros: Annotated[
+ Optional[dict[str, str]],
+ Field(description="Macro values to apply for this preview"),
+ ] = None
+ context_description: Annotated[
+ Optional[str],
+ Field(
+ description="Natural language description of the context for AI-generated content"
+ ),
+ ] = None
+
+
+class Asset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ creative_id: Annotated[str, Field(description="Unique identifier for the creative")]
+ name: Annotated[str, Field(description="Human-readable creative name")]
+ format_id: Annotated[
+ FormatId,
+ Field(
+ description="Structured format identifier with agent URL and format name",
+ title="Format ID",
+ ),
+ ]
+ assets: Annotated[
+ dict[
+ str,
+ Union[
+ Assets,
+ Assets12,
+ Assets13,
+ Assets14,
+ Assets15,
+ Assets16,
+ Assets17,
+ Assets18,
+ Assets19,
+ ],
+ ],
+ Field(description="Assets required by the format, keyed by asset_role"),
+ ]
+ inputs: Annotated[
+ Optional[list[Input]],
+ Field(
+ description="Preview contexts for generative formats - defines what scenarios to generate previews for"
+ ),
+ ] = None
+ tags: Annotated[
+ Optional[list[str]],
+ Field(description="User-defined tags for organization and searchability"),
+ ] = None
+ approved: Annotated[
+ Optional[bool],
+ Field(
+ description="For generative creatives: set to true to approve and finalize, false to request regeneration with updated assets/message. Omit for non-generative creatives."
+ ),
+ ] = None
+
+
+class AddCreativeAssetsRequest1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ media_buy_id: Annotated[
+ str, Field(description="Publisher's ID of the media buy to add creatives to")
+ ]
+ buyer_ref: Annotated[
+ Optional[str], Field(description="Buyer's reference for the media buy")
+ ] = None
+ assets: Annotated[
+ list[Asset], Field(description="Array of creative assets to upload")
+ ]
+
+
+class Assets20(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["image"]
+ url: Annotated[AnyUrl, Field(description="URL to the image asset")]
+ width: Annotated[
+ Optional[int], Field(description="Image width in pixels", ge=1)
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image height in pixels", ge=1)
+ ] = None
+ format: Annotated[
+ Optional[str],
+ Field(description="Image file format (jpg, png, gif, webp, etc.)"),
+ ] = None
+ alt_text: Annotated[
+ Optional[str], Field(description="Alternative text for accessibility")
+ ] = None
+
+
+class Assets21(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["video"]
+ url: Annotated[AnyUrl, Field(description="URL to the video asset")]
+ width: Annotated[
+ Optional[int], Field(description="Video width in pixels", ge=1)
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Video height in pixels", ge=1)
+ ] = None
+ duration_ms: Annotated[
+ Optional[int], Field(description="Video duration in milliseconds", ge=0)
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="Video file format (mp4, webm, mov, etc.)")
+ ] = None
+ bitrate_kbps: Annotated[
+ Optional[int], Field(description="Video bitrate in kilobits per second", ge=1)
+ ] = None
+
+
+class Assets22(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["audio"]
+ url: Annotated[AnyUrl, Field(description="URL to the audio asset")]
+ duration_ms: Annotated[
+ Optional[int], Field(description="Audio duration in milliseconds", ge=0)
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="Audio file format (mp3, wav, aac, etc.)")
+ ] = None
+ bitrate_kbps: Annotated[
+ Optional[int], Field(description="Audio bitrate in kilobits per second", ge=1)
+ ] = None
+
+
+class Assets23(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["text"]
+ content: Annotated[str, Field(description="Text content")]
+ max_length: Annotated[
+ Optional[int], Field(description="Maximum character length constraint", ge=1)
+ ] = None
+ language: Annotated[
+ Optional[str], Field(description="Language code (e.g., 'en', 'es', 'fr')")
+ ] = None
+
+
+class Assets24(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["html"]
+ content: Annotated[str, Field(description="HTML content")]
+ version: Annotated[
+ Optional[str], Field(description="HTML version (e.g., 'HTML5')")
+ ] = None
+
+
+class Assets25(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["css"]
+ content: Annotated[str, Field(description="CSS content")]
+ media: Annotated[
+ Optional[str],
+ Field(description="CSS media query context (e.g., 'screen', 'print')"),
+ ] = None
+
+
+class Assets26(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["javascript"]
+ content: Annotated[str, Field(description="JavaScript content")]
+ module_type: Annotated[
+ Optional[ModuleType], Field(description="JavaScript module type")
+ ] = None
+
+
+class Assets27(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["promoted_offerings"]
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="URL of the advertiser's brand or offering (e.g., https://retailer.com)"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand colors")] = None
+ fonts: Annotated[Optional[list[str]], Field(description="Brand fonts")] = None
+ tone: Annotated[Optional[str], Field(description="Brand tone/voice")] = None
+
+
+class Assets28(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_type: Literal["url"]
+ url: Annotated[AnyUrl, Field(description="URL reference")]
+ description: Annotated[
+ Optional[str], Field(description="Description of what this URL points to")
+ ] = None
+
+
+class Asset6(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ creative_id: Annotated[str, Field(description="Unique identifier for the creative")]
+ name: Annotated[str, Field(description="Human-readable creative name")]
+ format_id: Annotated[
+ FormatId,
+ Field(
+ description="Structured format identifier with agent URL and format name",
+ title="Format ID",
+ ),
+ ]
+ assets: Annotated[
+ dict[
+ str,
+ Union[
+ Assets20,
+ Assets21,
+ Assets22,
+ Assets23,
+ Assets24,
+ Assets25,
+ Assets26,
+ Assets27,
+ Assets28,
+ ],
+ ],
+ Field(description="Assets required by the format, keyed by asset_role"),
+ ]
+ inputs: Annotated[
+ Optional[list[Input]],
+ Field(
+ description="Preview contexts for generative formats - defines what scenarios to generate previews for"
+ ),
+ ] = None
+ tags: Annotated[
+ Optional[list[str]],
+ Field(description="User-defined tags for organization and searchability"),
+ ] = None
+ approved: Annotated[
+ Optional[bool],
+ Field(
+ description="For generative creatives: set to true to approve and finalize, false to request regeneration with updated assets/message. Omit for non-generative creatives."
+ ),
+ ] = None
+
+
+class AddCreativeAssetsRequest2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ media_buy_id: Annotated[
+ Optional[str],
+ Field(description="Publisher's ID of the media buy to add creatives to"),
+ ] = None
+ buyer_ref: Annotated[str, Field(description="Buyer's reference for the media buy")]
+ assets: Annotated[
+ list[Asset6], Field(description="Array of creative assets to upload")
+ ]
+
+
+class AddCreativeAssetsRequest(
+ RootModel[Union[AddCreativeAssetsRequest1, AddCreativeAssetsRequest2]]
+):
+ root: Annotated[
+ Union[AddCreativeAssetsRequest1, AddCreativeAssetsRequest2],
+ Field(
+ description="Request parameters for uploading creative assets",
+ title="Add Creative Assets Request",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_add_creative_assets_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_add_creative_assets_response_json.py
new file mode 100644
index 0000000..be2ab4f
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_add_creative_assets_response_json.py
@@ -0,0 +1,67 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_add-creative-assets-response_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class Status(Enum):
+ processing = "processing"
+ approved = "approved"
+ rejected = "rejected"
+ pending_review = "pending_review"
+
+
+class SuggestedAdaptation(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adaptation_id: Annotated[
+ str, Field(description="Unique identifier for this adaptation")
+ ]
+ format_id: Annotated[str, Field(description="Target format ID for the adaptation")]
+ name: Annotated[str, Field(description="Suggested name for the adapted creative")]
+ description: Annotated[str, Field(description="What this adaptation does")]
+ changes_summary: Annotated[
+ list[str], Field(description="List of changes that will be made")
+ ]
+ rationale: Annotated[str, Field(description="Why this adaptation is recommended")]
+ estimated_performance_lift: Annotated[
+ Optional[float],
+ Field(description="Expected performance improvement (percentage)", ge=0.0),
+ ] = None
+
+
+class AssetStatus(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ creative_id: Annotated[str, Field(description="The creative ID from the request")]
+ status: Annotated[
+ Status, Field(description="Status of a creative asset", title="Creative Status")
+ ]
+ platform_id: Annotated[
+ Optional[str],
+ Field(description="Platform-specific ID assigned to the creative"),
+ ] = None
+ review_feedback: Annotated[
+ Optional[str], Field(description="Feedback from platform review (if any)")
+ ] = None
+ suggested_adaptations: Annotated[
+ Optional[list[SuggestedAdaptation]],
+ Field(description="Array of recommended format adaptations"),
+ ] = None
+
+
+class AddCreativeAssetsResponse(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_statuses: Annotated[
+ list[AssetStatus],
+ Field(description="Array of status information for each uploaded asset"),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_create_media_buy_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_create_media_buy_request_json.py
new file mode 100644
index 0000000..d328c5e
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_create_media_buy_request_json.py
@@ -0,0 +1,1168 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_create-media-buy-request_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional, Union
+
+from pydantic import (
+ AnyUrl,
+ AwareDatetime,
+ BaseModel,
+ ConfigDict,
+ EmailStr,
+ Field,
+ RootModel,
+)
+
+
+class Pacing(Enum):
+ even = "even"
+ asap = "asap"
+ front_loaded = "front_loaded"
+
+
+class Budget(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ total: Annotated[float, Field(description="Total budget amount", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ pacing: Annotated[
+ Optional[Pacing], Field(description="Budget pacing strategy", title="Pacing")
+ ] = None
+
+
+class GeoCountryAnyOfItem(RootModel[str]):
+ root: Annotated[str, Field(pattern="^[A-Z]{2}$")]
+
+
+class FrequencyCap(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ suppress_minutes: Annotated[
+ float, Field(description="Minutes to suppress after impression", ge=0.0)
+ ]
+
+
+class TargetingOverlay(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ geo_country_any_of: Annotated[
+ Optional[list[GeoCountryAnyOfItem]],
+ Field(
+ description="Restrict delivery to specific countries (ISO codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_region_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific regions/states. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_metro_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific metro areas (DMA codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_postal_code_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific postal/ZIP codes. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ frequency_cap: Annotated[
+ Optional[FrequencyCap],
+ Field(
+ description="Frequency capping settings for package-level application",
+ title="Frequency Cap",
+ ),
+ ] = None
+
+
+class Packages(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ buyer_ref: Annotated[
+ str, Field(description="Buyer's reference identifier for this package")
+ ]
+ products: Annotated[
+ list[str], Field(description="Array of product IDs to include in this package")
+ ]
+ format_ids: Annotated[
+ list[str],
+ Field(
+ description="Array of format IDs that will be used for this package - must be supported by all products"
+ ),
+ ]
+ budget: Annotated[
+ Optional[Budget],
+ Field(
+ description="Budget configuration for a media buy or package",
+ title="Budget",
+ ),
+ ] = None
+ targeting_overlay: Annotated[
+ Optional[TargetingOverlay],
+ Field(
+ description="Optional geographic refinements for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are primarily for geographic restrictions (RCT testing, regulatory compliance).",
+ title="Targeting Overlay",
+ ),
+ ] = None
+ creative_ids: Annotated[
+ Optional[list[str]],
+ Field(description="Creative IDs to assign to this package at creation time"),
+ ] = None
+
+
+class Packages1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ buyer_ref: Annotated[
+ str, Field(description="Buyer's reference identifier for this package")
+ ]
+ products: Annotated[
+ list[str], Field(description="Array of product IDs to include in this package")
+ ]
+ format_selection: Annotated[
+ dict[str, Any], Field(description="Dynamic format selection criteria")
+ ]
+ budget: Annotated[
+ Optional[Any],
+ Field(description="Circular reference to /schemas/v1/core/budget.json"),
+ ] = None
+ targeting_overlay: Annotated[
+ Optional[Any],
+ Field(description="Circular reference to /schemas/v1/core/targeting.json"),
+ ] = None
+ creative_ids: Annotated[
+ Optional[list[str]],
+ Field(description="Creative IDs to assign to this package at creation time"),
+ ] = None
+
+
+class Logo(BaseModel):
+ url: Annotated[AnyUrl, Field(description="URL to the logo asset")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Semantic tags describing the logo variant (e.g., 'dark', 'light', 'square', 'horizontal', 'icon')"
+ ),
+ ] = None
+ width: Annotated[Optional[int], Field(description="Logo width in pixels")] = None
+ height: Annotated[Optional[int], Field(description="Logo height in pixels")] = None
+
+
+class Colors(BaseModel):
+ primary: Annotated[
+ Optional[str],
+ Field(
+ description="Primary brand color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"
+ ),
+ ] = None
+ secondary: Annotated[
+ Optional[str],
+ Field(
+ description="Secondary brand color (hex format)",
+ pattern="^#[0-9A-Fa-f]{6}$",
+ ),
+ ] = None
+ accent: Annotated[
+ Optional[str],
+ Field(description="Accent color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+ background: Annotated[
+ Optional[str],
+ Field(description="Background color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+ text: Annotated[
+ Optional[str],
+ Field(description="Text color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+
+
+class Fonts(BaseModel):
+ primary: Annotated[Optional[str], Field(description="Primary font family name")] = (
+ None
+ )
+ secondary: Annotated[
+ Optional[str], Field(description="Secondary font family name")
+ ] = None
+ font_urls: Annotated[
+ Optional[list[AnyUrl]],
+ Field(description="URLs to web font files if using custom fonts"),
+ ] = None
+
+
+class AssetType(Enum):
+ image = "image"
+ video = "video"
+ audio = "audio"
+ text = "text"
+
+
+class Asset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class FeedFormat(Enum):
+ google_merchant_center = "google_merchant_center"
+ facebook_catalog = "facebook_catalog"
+ custom = "custom"
+
+
+class UpdateFrequency(Enum):
+ realtime = "realtime"
+ hourly = "hourly"
+ daily = "daily"
+ weekly = "weekly"
+
+
+class ProductCatalog(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class Disclaimer(BaseModel):
+ text: Annotated[str, Field(description="Disclaimer text")]
+ context: Annotated[
+ Optional[str],
+ Field(
+ description="When this disclaimer applies (e.g., 'financial_products', 'health_claims', 'all')"
+ ),
+ ] = None
+ required: Annotated[
+ Optional[bool], Field(description="Whether this disclaimer must appear")
+ ] = True
+
+
+class Contact(BaseModel):
+ email: Annotated[Optional[EmailStr], Field(description="Contact email")] = None
+ phone: Annotated[Optional[str], Field(description="Contact phone number")] = None
+
+
+class Metadata(BaseModel):
+ created_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this brand manifest was created"),
+ ] = None
+ updated_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this brand manifest was last updated"),
+ ] = None
+ version: Annotated[
+ Optional[str], Field(description="Brand card version number")
+ ] = None
+
+
+class BrandManifest(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ AnyUrl,
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ]
+ name: Annotated[Optional[str], Field(description="Brand or business name")] = None
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Asset8(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class ProductCatalog5(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class BrandManifest4(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ] = None
+ name: Annotated[str, Field(description="Brand or business name")]
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset8]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog5],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Scheme(Enum):
+ bearer = "Bearer"
+ hmac_sha256 = "HMAC-SHA256"
+
+
+class Authentication(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ schemes: Annotated[
+ list[Scheme],
+ Field(
+ description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for signature verification (recommended for production)",
+ max_length=1,
+ min_length=1,
+ ),
+ ]
+ credentials: Annotated[
+ str,
+ Field(
+ description="Credentials for authentication. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.",
+ min_length=32,
+ ),
+ ]
+
+
+class ReportingFrequency(Enum):
+ hourly = "hourly"
+ daily = "daily"
+ monthly = "monthly"
+
+
+class RequestedMetric(Enum):
+ impressions = "impressions"
+ spend = "spend"
+ clicks = "clicks"
+ ctr = "ctr"
+ video_completions = "video_completions"
+ completion_rate = "completion_rate"
+ conversions = "conversions"
+ viewability = "viewability"
+ engagement_rate = "engagement_rate"
+
+
+class ReportingWebhook(BaseModel):
+ url: Annotated[
+ AnyUrl, Field(description="Webhook endpoint URL for task status notifications")
+ ]
+ token: Annotated[
+ Optional[str],
+ Field(
+ description="Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.",
+ min_length=16,
+ ),
+ ] = None
+ authentication: Annotated[
+ Authentication,
+ Field(
+ description="Authentication configuration for webhook delivery (A2A-compatible)"
+ ),
+ ]
+ reporting_frequency: Annotated[
+ ReportingFrequency,
+ Field(
+ description="Frequency for automated reporting delivery. Must be supported by all products in the media buy."
+ ),
+ ]
+ requested_metrics: Annotated[
+ Optional[list[RequestedMetric]],
+ Field(
+ description="Optional list of metrics to include in webhook notifications. If omitted, all available metrics are included. Must be subset of product's available_metrics."
+ ),
+ ] = None
+
+
+class CreateMediaBuyRequest1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ Optional[str],
+ Field(
+ description="AdCP schema version for this request",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ] = "1.6.1"
+ buyer_ref: Annotated[
+ str, Field(description="Buyer's reference identifier for this media buy")
+ ]
+ packages: Annotated[
+ list[Union[Packages, Packages1]],
+ Field(description="Array of package configurations"),
+ ]
+ brand_manifest: Annotated[
+ Optional[Union[Union[BrandManifest, BrandManifest4], AnyUrl]],
+ Field(
+ description="Brand manifest provided either as an inline object or a URL string pointing to a hosted manifest",
+ examples=[
+ {
+ "description": "Inline brand manifest",
+ "data": {
+ "url": "https://acmecorp.com",
+ "name": "ACME Corporation",
+ "colors": {"primary": "#FF6B35"},
+ },
+ },
+ {
+ "description": "URL string reference to hosted manifest",
+ "data": "https://cdn.acmecorp.com/brand-manifest.json",
+ },
+ ],
+ title="Brand Manifest Reference",
+ ),
+ ] = None
+ promoted_offering: Annotated[
+ str,
+ Field(
+ description="DEPRECATED: Use brand_manifest instead. Legacy field for describing what is being promoted."
+ ),
+ ]
+ po_number: Annotated[
+ Optional[str], Field(description="Purchase order number for tracking")
+ ] = None
+ start_time: Annotated[
+ Union[str, AwareDatetime],
+ Field(
+ description="Campaign start timing: 'asap' or ISO 8601 date-time",
+ title="Start Timing",
+ ),
+ ]
+ end_time: Annotated[
+ AwareDatetime, Field(description="Campaign end date/time in ISO 8601 format")
+ ]
+ budget: Annotated[
+ float,
+ Field(
+ description="Total budget for this media buy. Currency is determined by the pricing_option_id selected in each package.",
+ ge=0.0,
+ ),
+ ]
+ reporting_webhook: Optional[ReportingWebhook] = None
+
+
+class Budget2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ total: Annotated[float, Field(description="Total budget amount", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ pacing: Annotated[
+ Optional[Pacing], Field(description="Budget pacing strategy", title="Pacing")
+ ] = None
+
+
+class TargetingOverlay3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ geo_country_any_of: Annotated[
+ Optional[list[GeoCountryAnyOfItem]],
+ Field(
+ description="Restrict delivery to specific countries (ISO codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_region_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific regions/states. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_metro_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific metro areas (DMA codes). Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ geo_postal_code_any_of: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Restrict delivery to specific postal/ZIP codes. Use for regulatory compliance or RCT testing."
+ ),
+ ] = None
+ frequency_cap: Annotated[
+ Optional[FrequencyCap],
+ Field(
+ description="Frequency capping settings for package-level application",
+ title="Frequency Cap",
+ ),
+ ] = None
+
+
+class Packages2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ buyer_ref: Annotated[
+ str, Field(description="Buyer's reference identifier for this package")
+ ]
+ products: Annotated[
+ list[str], Field(description="Array of product IDs to include in this package")
+ ]
+ format_ids: Annotated[
+ list[str],
+ Field(
+ description="Array of format IDs that will be used for this package - must be supported by all products"
+ ),
+ ]
+ budget: Annotated[
+ Optional[Budget2],
+ Field(
+ description="Budget configuration for a media buy or package",
+ title="Budget",
+ ),
+ ] = None
+ targeting_overlay: Annotated[
+ Optional[TargetingOverlay3],
+ Field(
+ description="Optional geographic refinements for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are primarily for geographic restrictions (RCT testing, regulatory compliance).",
+ title="Targeting Overlay",
+ ),
+ ] = None
+ creative_ids: Annotated[
+ Optional[list[str]],
+ Field(description="Creative IDs to assign to this package at creation time"),
+ ] = None
+
+
+class Packages3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ buyer_ref: Annotated[
+ str, Field(description="Buyer's reference identifier for this package")
+ ]
+ products: Annotated[
+ list[str], Field(description="Array of product IDs to include in this package")
+ ]
+ format_selection: Annotated[
+ dict[str, Any], Field(description="Dynamic format selection criteria")
+ ]
+ budget: Annotated[
+ Optional[Any],
+ Field(description="Circular reference to /schemas/v1/core/budget.json"),
+ ] = None
+ targeting_overlay: Annotated[
+ Optional[Any],
+ Field(description="Circular reference to /schemas/v1/core/targeting.json"),
+ ] = None
+ creative_ids: Annotated[
+ Optional[list[str]],
+ Field(description="Creative IDs to assign to this package at creation time"),
+ ] = None
+
+
+class Asset9(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class ProductCatalog6(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class BrandManifest5(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ AnyUrl,
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ]
+ name: Annotated[Optional[str], Field(description="Brand or business name")] = None
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset9]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog6],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Asset10(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class ProductCatalog7(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class BrandManifest6(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ] = None
+ name: Annotated[str, Field(description="Brand or business name")]
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset10]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog7],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Authentication2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ schemes: Annotated[
+ list[Scheme],
+ Field(
+ description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for signature verification (recommended for production)",
+ max_length=1,
+ min_length=1,
+ ),
+ ]
+ credentials: Annotated[
+ str,
+ Field(
+ description="Credentials for authentication. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.",
+ min_length=32,
+ ),
+ ]
+
+
+class ReportingWebhook1(BaseModel):
+ url: Annotated[
+ AnyUrl, Field(description="Webhook endpoint URL for task status notifications")
+ ]
+ token: Annotated[
+ Optional[str],
+ Field(
+ description="Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.",
+ min_length=16,
+ ),
+ ] = None
+ authentication: Annotated[
+ Authentication2,
+ Field(
+ description="Authentication configuration for webhook delivery (A2A-compatible)"
+ ),
+ ]
+ reporting_frequency: Annotated[
+ ReportingFrequency,
+ Field(
+ description="Frequency for automated reporting delivery. Must be supported by all products in the media buy."
+ ),
+ ]
+ requested_metrics: Annotated[
+ Optional[list[RequestedMetric]],
+ Field(
+ description="Optional list of metrics to include in webhook notifications. If omitted, all available metrics are included. Must be subset of product's available_metrics."
+ ),
+ ] = None
+
+
+class CreateMediaBuyRequest2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ Optional[str],
+ Field(
+ description="AdCP schema version for this request",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ] = "1.6.1"
+ buyer_ref: Annotated[
+ str, Field(description="Buyer's reference identifier for this media buy")
+ ]
+ packages: Annotated[
+ list[Union[Packages2, Packages3]],
+ Field(description="Array of package configurations"),
+ ]
+ brand_manifest: Annotated[
+ Union[Union[BrandManifest5, BrandManifest6], AnyUrl],
+ Field(
+ description="Brand manifest provided either as an inline object or a URL string pointing to a hosted manifest",
+ examples=[
+ {
+ "description": "Inline brand manifest",
+ "data": {
+ "url": "https://acmecorp.com",
+ "name": "ACME Corporation",
+ "colors": {"primary": "#FF6B35"},
+ },
+ },
+ {
+ "description": "URL string reference to hosted manifest",
+ "data": "https://cdn.acmecorp.com/brand-manifest.json",
+ },
+ ],
+ title="Brand Manifest Reference",
+ ),
+ ]
+ promoted_offering: Annotated[
+ Optional[str],
+ Field(
+ description="DEPRECATED: Use brand_manifest instead. Legacy field for describing what is being promoted."
+ ),
+ ] = None
+ po_number: Annotated[
+ Optional[str], Field(description="Purchase order number for tracking")
+ ] = None
+ start_time: Annotated[
+ Union[str, AwareDatetime],
+ Field(
+ description="Campaign start timing: 'asap' or ISO 8601 date-time",
+ title="Start Timing",
+ ),
+ ]
+ end_time: Annotated[
+ AwareDatetime, Field(description="Campaign end date/time in ISO 8601 format")
+ ]
+ budget: Annotated[
+ float,
+ Field(
+ description="Total budget for this media buy. Currency is determined by the pricing_option_id selected in each package.",
+ ge=0.0,
+ ),
+ ]
+ reporting_webhook: Optional[ReportingWebhook1] = None
+
+
+class CreateMediaBuyRequest(
+ RootModel[Union[CreateMediaBuyRequest1, CreateMediaBuyRequest2]]
+):
+ root: Annotated[
+ Union[CreateMediaBuyRequest1, CreateMediaBuyRequest2],
+ Field(
+ description="Request parameters for creating a media buy",
+ title="Create Media Buy Request",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_create_media_buy_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_create_media_buy_response_json.py
new file mode 100644
index 0000000..b5c9475
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_create_media_buy_response_json.py
@@ -0,0 +1,103 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_create-media-buy-response_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional
+
+from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
+
+
+class Status(Enum):
+ submitted = "submitted"
+ working = "working"
+ input_required = "input-required"
+ completed = "completed"
+ canceled = "canceled"
+ failed = "failed"
+ rejected = "rejected"
+ auth_required = "auth-required"
+ unknown = "unknown"
+
+
+class Package(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ package_id: Annotated[
+ str, Field(description="Publisher's unique identifier for the package")
+ ]
+ buyer_ref: Annotated[
+ str, Field(description="Buyer's reference identifier for the package")
+ ]
+
+
+class Error(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ code: Annotated[str, Field(description="Error code for programmatic handling")]
+ message: Annotated[str, Field(description="Human-readable error message")]
+ field: Annotated[
+ Optional[str],
+ Field(
+ description="Field path associated with the error (e.g., 'packages[0].targeting')"
+ ),
+ ] = None
+ suggestion: Annotated[
+ Optional[str], Field(description="Suggested fix for the error")
+ ] = None
+ retry_after: Annotated[
+ Optional[float],
+ Field(description="Seconds to wait before retrying the operation", ge=0.0),
+ ] = None
+ details: Annotated[
+ Optional[Any], Field(description="Additional task-specific error details")
+ ] = None
+
+
+class CreateMediaBuyResponse(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ str,
+ Field(
+ description="AdCP schema version used for this response",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ]
+ status: Annotated[
+ Status,
+ Field(
+ description="Standardized task status values based on A2A TaskState enum. Indicates the current state of any AdCP operation.",
+ title="Task Status",
+ ),
+ ]
+ task_id: Annotated[
+ Optional[str],
+ Field(
+ description="Unique identifier for tracking this async operation (present for submitted/working status)"
+ ),
+ ] = None
+ media_buy_id: Annotated[
+ Optional[str],
+ Field(description="Publisher's unique identifier for the created media buy"),
+ ] = None
+ buyer_ref: Annotated[
+ str, Field(description="Buyer's reference identifier for this media buy")
+ ]
+ creative_deadline: Annotated[
+ Optional[AwareDatetime],
+ Field(description="ISO 8601 timestamp for creative upload deadline"),
+ ] = None
+ packages: Annotated[
+ Optional[list[Package]], Field(description="Array of created packages")
+ ] = None
+ errors: Annotated[
+ Optional[list[Error]],
+ Field(
+ description="Task-specific errors and warnings (e.g., partial package creation failures)"
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_media_buy_delivery_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_media_buy_delivery_request_json.py
new file mode 100644
index 0000000..dbbf15b
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_media_buy_delivery_request_json.py
@@ -0,0 +1,67 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_get-media-buy-delivery-request_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional, Union
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class StatusFilter(Enum):
+ active = "active"
+ pending = "pending"
+ paused = "paused"
+ completed = "completed"
+ failed = "failed"
+ all = "all"
+
+
+class StatusFilterEnum(Enum):
+ active = "active"
+ pending = "pending"
+ paused = "paused"
+ completed = "completed"
+ failed = "failed"
+
+
+class GetMediaBuyDeliveryRequest(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ Optional[str],
+ Field(
+ description="AdCP schema version for this request",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ] = "1.6.0"
+ media_buy_ids: Annotated[
+ Optional[list[str]],
+ Field(description="Array of publisher media buy IDs to get delivery data for"),
+ ] = None
+ buyer_refs: Annotated[
+ Optional[list[str]],
+ Field(description="Array of buyer reference IDs to get delivery data for"),
+ ] = None
+ status_filter: Annotated[
+ Optional[Union[StatusFilter, list[StatusFilterEnum]]],
+ Field(
+ description="Filter by status. Can be a single status or array of statuses"
+ ),
+ ] = None
+ start_date: Annotated[
+ Optional[str],
+ Field(
+ description="Start date for reporting period (YYYY-MM-DD)",
+ pattern="^\\d{4}-\\d{2}-\\d{2}$",
+ ),
+ ] = None
+ end_date: Annotated[
+ Optional[str],
+ Field(
+ description="End date for reporting period (YYYY-MM-DD)",
+ pattern="^\\d{4}-\\d{2}-\\d{2}$",
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_media_buy_delivery_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_media_buy_delivery_response_json.py
new file mode 100644
index 0000000..93f7761
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_media_buy_delivery_response_json.py
@@ -0,0 +1,389 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_get-media-buy-delivery-response_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional
+
+from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
+
+
+class NotificationType(Enum):
+ scheduled = "scheduled"
+ final = "final"
+ delayed = "delayed"
+ adjusted = "adjusted"
+
+
+class ReportingPeriod(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ start: Annotated[
+ AwareDatetime,
+ Field(
+ description="ISO 8601 start timestamp in UTC (e.g., 2024-02-05T00:00:00Z)"
+ ),
+ ]
+ end: Annotated[
+ AwareDatetime,
+ Field(description="ISO 8601 end timestamp in UTC (e.g., 2024-02-05T23:59:59Z)"),
+ ]
+
+
+class AggregatedTotals(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ impressions: Annotated[
+ float,
+ Field(description="Total impressions delivered across all media buys", ge=0.0),
+ ]
+ spend: Annotated[
+ float, Field(description="Total amount spent across all media buys", ge=0.0)
+ ]
+ clicks: Annotated[
+ Optional[float],
+ Field(description="Total clicks across all media buys (if applicable)", ge=0.0),
+ ] = None
+ video_completions: Annotated[
+ Optional[float],
+ Field(
+ description="Total video completions across all media buys (if applicable)",
+ ge=0.0,
+ ),
+ ] = None
+ media_buy_count: Annotated[
+ int, Field(description="Number of media buys included in the response", ge=0)
+ ]
+
+
+class Status(Enum):
+ pending = "pending"
+ active = "active"
+ paused = "paused"
+ completed = "completed"
+ failed = "failed"
+ reporting_delayed = "reporting_delayed"
+
+
+class PricingModel(Enum):
+ cpm = "cpm"
+ cpc = "cpc"
+ cpcv = "cpcv"
+ cpv = "cpv"
+ cpp = "cpp"
+ flat_rate = "flat_rate"
+
+
+class QuartileData(BaseModel):
+ q1_views: Annotated[
+ Optional[float], Field(description="25% completion views", ge=0.0)
+ ] = None
+ q2_views: Annotated[
+ Optional[float], Field(description="50% completion views", ge=0.0)
+ ] = None
+ q3_views: Annotated[
+ Optional[float], Field(description="75% completion views", ge=0.0)
+ ] = None
+ q4_views: Annotated[
+ Optional[float], Field(description="100% completion views", ge=0.0)
+ ] = None
+
+
+class VenueBreakdownItem(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ venue_id: Annotated[str, Field(description="Venue identifier")]
+ venue_name: Annotated[
+ Optional[str], Field(description="Human-readable venue name")
+ ] = None
+ venue_type: Annotated[
+ Optional[str],
+ Field(
+ description="Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')"
+ ),
+ ] = None
+ impressions: Annotated[
+ int, Field(description="Impressions delivered at this venue", ge=0)
+ ]
+ loop_plays: Annotated[
+ Optional[int], Field(description="Loop plays at this venue", ge=0)
+ ] = None
+ screens_used: Annotated[
+ Optional[int], Field(description="Number of screens used at this venue", ge=0)
+ ] = None
+
+
+class DoohMetrics(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ loop_plays: Annotated[
+ Optional[int], Field(description="Number of times ad played in rotation", ge=0)
+ ] = None
+ screens_used: Annotated[
+ Optional[int],
+ Field(description="Number of unique screens displaying the ad", ge=0),
+ ] = None
+ screen_time_seconds: Annotated[
+ Optional[int], Field(description="Total display time in seconds", ge=0)
+ ] = None
+ sov_achieved: Annotated[
+ Optional[float],
+ Field(
+ description="Actual share of voice delivered (0.0 to 1.0)", ge=0.0, le=1.0
+ ),
+ ] = None
+ calculation_notes: Annotated[
+ Optional[str],
+ Field(description="Explanation of how DOOH impressions were calculated"),
+ ] = None
+ venue_breakdown: Annotated[
+ Optional[list[VenueBreakdownItem]],
+ Field(description="Per-venue performance breakdown"),
+ ] = None
+
+
+class Totals(BaseModel):
+ impressions: Annotated[
+ Optional[float], Field(description="Impressions delivered", ge=0.0)
+ ] = None
+ spend: Annotated[Optional[float], Field(description="Amount spent", ge=0.0)] = None
+ clicks: Annotated[Optional[float], Field(description="Total clicks", ge=0.0)] = None
+ ctr: Annotated[
+ Optional[float],
+ Field(description="Click-through rate (clicks/impressions)", ge=0.0, le=1.0),
+ ] = None
+ views: Annotated[
+ Optional[float], Field(description="Views at threshold (for CPV)", ge=0.0)
+ ] = None
+ completed_views: Annotated[
+ Optional[float], Field(description="100% completions (for CPCV)", ge=0.0)
+ ] = None
+ video_completions: Annotated[
+ Optional[float],
+ Field(description="DEPRECATED: Use completed_views instead", ge=0.0),
+ ] = None
+ completion_rate: Annotated[
+ Optional[float],
+ Field(
+ description="Completion rate (completed_views/impressions)", ge=0.0, le=1.0
+ ),
+ ] = None
+ conversions: Annotated[
+ Optional[float],
+ Field(
+ description="Conversions (reserved for future CPA pricing support)", ge=0.0
+ ),
+ ] = None
+ leads: Annotated[
+ Optional[float],
+ Field(
+ description="Leads generated (reserved for future CPL pricing support)",
+ ge=0.0,
+ ),
+ ] = None
+ grps: Annotated[
+ Optional[float],
+ Field(description="Gross Rating Points delivered (for CPP)", ge=0.0),
+ ] = None
+ reach: Annotated[
+ Optional[float],
+ Field(
+ description="Unique reach - units depend on measurement provider (e.g., individuals, households, devices, cookies). See delivery_measurement.provider for methodology.",
+ ge=0.0,
+ ),
+ ] = None
+ frequency: Annotated[
+ Optional[float],
+ Field(
+ description="Average frequency per individual (typically measured over campaign duration, but can vary by measurement provider)",
+ ge=0.0,
+ ),
+ ] = None
+ quartile_data: Annotated[
+ Optional[QuartileData], Field(description="Video quartile completion data")
+ ] = None
+ dooh_metrics: Annotated[
+ Optional[DoohMetrics],
+ Field(description="DOOH-specific metrics (only included for DOOH campaigns)"),
+ ] = None
+ effective_rate: Annotated[
+ Optional[float],
+ Field(
+ description="Effective rate paid per unit based on pricing_model (e.g., actual CPM for 'cpm', actual cost per completed view for 'cpcv', actual cost per point for 'cpp')",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class ByPackageItem(BaseModel):
+ package_id: Annotated[str, Field(description="Publisher's package identifier")]
+ buyer_ref: Annotated[
+ Optional[str],
+ Field(description="Buyer's reference identifier for this package"),
+ ] = None
+ pacing_index: Annotated[
+ Optional[float],
+ Field(
+ description="Delivery pace (1.0 = on track, <1.0 = behind, >1.0 = ahead)",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class DailyBreakdownItem(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ date: Annotated[
+ str, Field(description="Date (YYYY-MM-DD)", pattern="^\\d{4}-\\d{2}-\\d{2}$")
+ ]
+ impressions: Annotated[float, Field(description="Daily impressions", ge=0.0)]
+ spend: Annotated[float, Field(description="Daily spend", ge=0.0)]
+
+
+class MediaBuyDelivery(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ media_buy_id: Annotated[str, Field(description="Publisher's media buy identifier")]
+ buyer_ref: Annotated[
+ Optional[str],
+ Field(description="Buyer's reference identifier for this media buy"),
+ ] = None
+ status: Annotated[
+ Status,
+ Field(
+ description="Current media buy status. In webhook context, reporting_delayed indicates data temporarily unavailable."
+ ),
+ ]
+ message: Annotated[
+ Optional[str],
+ Field(
+ description="Human-readable message (typically present when status is reporting_delayed or failed)"
+ ),
+ ] = None
+ expected_availability: Annotated[
+ Optional[AwareDatetime],
+ Field(
+ description="When delayed data is expected to be available (only present when status is reporting_delayed)"
+ ),
+ ] = None
+ is_adjusted: Annotated[
+ Optional[bool],
+ Field(
+ description="Indicates this delivery contains updated data for a previously reported period. Buyer should replace previous period data with these totals."
+ ),
+ ] = None
+ pricing_model: Annotated[
+ Optional[PricingModel],
+ Field(
+ description="Supported pricing models for advertising products",
+ title="Pricing Model",
+ ),
+ ] = None
+ totals: Totals
+ by_package: Annotated[
+ list[ByPackageItem], Field(description="Metrics broken down by package")
+ ]
+ daily_breakdown: Annotated[
+ Optional[list[DailyBreakdownItem]], Field(description="Day-by-day delivery")
+ ] = None
+
+
+class Error(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ code: Annotated[str, Field(description="Error code for programmatic handling")]
+ message: Annotated[str, Field(description="Human-readable error message")]
+ field: Annotated[
+ Optional[str],
+ Field(
+ description="Field path associated with the error (e.g., 'packages[0].targeting')"
+ ),
+ ] = None
+ suggestion: Annotated[
+ Optional[str], Field(description="Suggested fix for the error")
+ ] = None
+ retry_after: Annotated[
+ Optional[float],
+ Field(description="Seconds to wait before retrying the operation", ge=0.0),
+ ] = None
+ details: Annotated[
+ Optional[Any], Field(description="Additional task-specific error details")
+ ] = None
+
+
+class GetMediaBuyDeliveryResponse(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ str,
+ Field(
+ description="AdCP schema version used for this response",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ]
+ notification_type: Annotated[
+ Optional[NotificationType],
+ Field(
+ description="Type of webhook notification (only present in webhook deliveries): scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = resending period with updated data"
+ ),
+ ] = None
+ partial_data: Annotated[
+ Optional[bool],
+ Field(
+ description="Indicates if any media buys in this webhook have missing/delayed data (only present in webhook deliveries)"
+ ),
+ ] = None
+ unavailable_count: Annotated[
+ Optional[int],
+ Field(
+ description="Number of media buys with reporting_delayed or failed status (only present in webhook deliveries when partial_data is true)",
+ ge=0,
+ ),
+ ] = None
+ sequence_number: Annotated[
+ Optional[int],
+ Field(
+ description="Sequential notification number (only present in webhook deliveries, starts at 1)",
+ ge=1,
+ ),
+ ] = None
+ next_expected_at: Annotated[
+ Optional[AwareDatetime],
+ Field(
+ description="ISO 8601 timestamp for next expected notification (only present in webhook deliveries when notification_type is not 'final')"
+ ),
+ ] = None
+ reporting_period: Annotated[
+ ReportingPeriod,
+ Field(description="Date range for the report. All periods use UTC timezone."),
+ ]
+ currency: Annotated[
+ str, Field(description="ISO 4217 currency code", pattern="^[A-Z]{3}$")
+ ]
+ aggregated_totals: Annotated[
+ Optional[AggregatedTotals],
+ Field(
+ description="Combined metrics across all returned media buys. Only included in API responses (get_media_buy_delivery), not in webhook notifications."
+ ),
+ ] = None
+ media_buy_deliveries: Annotated[
+ list[MediaBuyDelivery],
+ Field(
+ description="Array of delivery data for media buys. When used in webhook notifications, may contain multiple media buys aggregated by publisher. When used in get_media_buy_delivery API responses, typically contains requested media buys."
+ ),
+ ]
+ errors: Annotated[
+ Optional[list[Error]],
+ Field(
+ description="Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)"
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_products_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_products_request_json.py
new file mode 100644
index 0000000..93d5384
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_products_request_json.py
@@ -0,0 +1,814 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_get-products-request_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional, Union
+
+from pydantic import (
+ AnyUrl,
+ AwareDatetime,
+ BaseModel,
+ ConfigDict,
+ EmailStr,
+ Field,
+ RootModel,
+)
+
+
+class Logo(BaseModel):
+ url: Annotated[AnyUrl, Field(description="URL to the logo asset")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Semantic tags describing the logo variant (e.g., 'dark', 'light', 'square', 'horizontal', 'icon')"
+ ),
+ ] = None
+ width: Annotated[Optional[int], Field(description="Logo width in pixels")] = None
+ height: Annotated[Optional[int], Field(description="Logo height in pixels")] = None
+
+
+class Colors(BaseModel):
+ primary: Annotated[
+ Optional[str],
+ Field(
+ description="Primary brand color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"
+ ),
+ ] = None
+ secondary: Annotated[
+ Optional[str],
+ Field(
+ description="Secondary brand color (hex format)",
+ pattern="^#[0-9A-Fa-f]{6}$",
+ ),
+ ] = None
+ accent: Annotated[
+ Optional[str],
+ Field(description="Accent color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+ background: Annotated[
+ Optional[str],
+ Field(description="Background color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+ text: Annotated[
+ Optional[str],
+ Field(description="Text color (hex format)", pattern="^#[0-9A-Fa-f]{6}$"),
+ ] = None
+
+
+class Fonts(BaseModel):
+ primary: Annotated[Optional[str], Field(description="Primary font family name")] = (
+ None
+ )
+ secondary: Annotated[
+ Optional[str], Field(description="Secondary font family name")
+ ] = None
+ font_urls: Annotated[
+ Optional[list[AnyUrl]],
+ Field(description="URLs to web font files if using custom fonts"),
+ ] = None
+
+
+class AssetType(Enum):
+ image = "image"
+ video = "video"
+ audio = "audio"
+ text = "text"
+
+
+class Asset(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class FeedFormat(Enum):
+ google_merchant_center = "google_merchant_center"
+ facebook_catalog = "facebook_catalog"
+ custom = "custom"
+
+
+class UpdateFrequency(Enum):
+ realtime = "realtime"
+ hourly = "hourly"
+ daily = "daily"
+ weekly = "weekly"
+
+
+class ProductCatalog(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class Disclaimer(BaseModel):
+ text: Annotated[str, Field(description="Disclaimer text")]
+ context: Annotated[
+ Optional[str],
+ Field(
+ description="When this disclaimer applies (e.g., 'financial_products', 'health_claims', 'all')"
+ ),
+ ] = None
+ required: Annotated[
+ Optional[bool], Field(description="Whether this disclaimer must appear")
+ ] = True
+
+
+class Contact(BaseModel):
+ email: Annotated[Optional[EmailStr], Field(description="Contact email")] = None
+ phone: Annotated[Optional[str], Field(description="Contact phone number")] = None
+
+
+class Metadata(BaseModel):
+ created_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this brand manifest was created"),
+ ] = None
+ updated_date: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When this brand manifest was last updated"),
+ ] = None
+ version: Annotated[
+ Optional[str], Field(description="Brand card version number")
+ ] = None
+
+
+class BrandManifest(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ AnyUrl,
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ]
+ name: Annotated[Optional[str], Field(description="Brand or business name")] = None
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Asset12(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class ProductCatalog9(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class BrandManifest8(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ] = None
+ name: Annotated[str, Field(description="Brand or business name")]
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset12]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog9],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class DeliveryType(Enum):
+ guaranteed = "guaranteed"
+ non_guaranteed = "non_guaranteed"
+
+
+class FormatType(Enum):
+ video = "video"
+ display = "display"
+ audio = "audio"
+
+
+class Filters(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ delivery_type: Annotated[
+ Optional[DeliveryType],
+ Field(description="Type of inventory delivery", title="Delivery Type"),
+ ] = None
+ is_fixed_price: Annotated[
+ Optional[bool], Field(description="Filter for fixed price vs auction products")
+ ] = None
+ format_types: Annotated[
+ Optional[list[FormatType]], Field(description="Filter by format types")
+ ] = None
+ format_ids: Annotated[
+ Optional[list[str]], Field(description="Filter by specific format IDs")
+ ] = None
+ standard_formats_only: Annotated[
+ Optional[bool],
+ Field(description="Only return products accepting IAB standard formats"),
+ ] = None
+ min_exposures: Annotated[
+ Optional[int],
+ Field(
+ description="Minimum exposures/impressions needed for measurement validity",
+ ge=1,
+ ),
+ ] = None
+
+
+class GetProductsRequest1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ Optional[str],
+ Field(
+ description="AdCP schema version for this request",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ] = "1.6.0"
+ brief: Annotated[
+ Optional[str],
+ Field(description="Natural language description of campaign requirements"),
+ ] = None
+ promoted_offering: Annotated[
+ str,
+ Field(
+ description="DEPRECATED: Use brand_manifest instead. Legacy field for describing what is being promoted."
+ ),
+ ]
+ brand_manifest: Annotated[
+ Optional[Union[Union[BrandManifest, BrandManifest8], AnyUrl]],
+ Field(
+ description="Brand manifest provided either as an inline object or a URL string pointing to a hosted manifest",
+ examples=[
+ {
+ "description": "Inline brand manifest",
+ "data": {
+ "url": "https://acmecorp.com",
+ "name": "ACME Corporation",
+ "colors": {"primary": "#FF6B35"},
+ },
+ },
+ {
+ "description": "URL string reference to hosted manifest",
+ "data": "https://cdn.acmecorp.com/brand-manifest.json",
+ },
+ ],
+ title="Brand Manifest Reference",
+ ),
+ ] = None
+ filters: Annotated[
+ Optional[Filters], Field(description="Structured filters for product discovery")
+ ] = None
+
+
+class Asset13(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class ProductCatalog10(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class BrandManifest9(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ AnyUrl,
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ]
+ name: Annotated[Optional[str], Field(description="Brand or business name")] = None
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset13]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog10],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Asset14(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ asset_id: Annotated[str, Field(description="Unique identifier for this asset")]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ url: Annotated[AnyUrl, Field(description="URL to CDN-hosted asset file")]
+ tags: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Tags for asset discovery (e.g., 'holiday', 'lifestyle', 'product_shot')"
+ ),
+ ] = None
+ name: Annotated[Optional[str], Field(description="Human-readable asset name")] = (
+ None
+ )
+ description: Annotated[
+ Optional[str], Field(description="Asset description or usage notes")
+ ] = None
+ width: Annotated[
+ Optional[int], Field(description="Image/video width in pixels")
+ ] = None
+ height: Annotated[
+ Optional[int], Field(description="Image/video height in pixels")
+ ] = None
+ duration_seconds: Annotated[
+ Optional[float], Field(description="Video/audio duration in seconds")
+ ] = None
+ file_size_bytes: Annotated[
+ Optional[int], Field(description="File size in bytes")
+ ] = None
+ format: Annotated[
+ Optional[str], Field(description="File format (e.g., 'jpg', 'mp4', 'mp3')")
+ ] = None
+ metadata: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Additional asset-specific metadata"),
+ ] = None
+
+
+class ProductCatalog11(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ feed_url: Annotated[AnyUrl, Field(description="URL to product catalog feed")]
+ feed_format: Annotated[
+ Optional[FeedFormat], Field(description="Format of the product feed")
+ ] = "google_merchant_center"
+ categories: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Product categories available in the catalog (for filtering)"
+ ),
+ ] = None
+ last_updated: Annotated[
+ Optional[AwareDatetime],
+ Field(description="When the product catalog was last updated"),
+ ] = None
+ update_frequency: Annotated[
+ Optional[UpdateFrequency],
+ Field(description="How frequently the product catalog is updated"),
+ ] = None
+
+
+class BrandManifest10(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Primary brand URL for context and asset discovery. Creative agents can infer brand information from this URL."
+ ),
+ ] = None
+ name: Annotated[str, Field(description="Brand or business name")]
+ logos: Annotated[
+ Optional[list[Logo]],
+ Field(
+ description="Brand logo assets with semantic tags for different use cases"
+ ),
+ ] = None
+ colors: Annotated[Optional[Colors], Field(description="Brand color palette")] = None
+ fonts: Annotated[
+ Optional[Fonts], Field(description="Brand typography guidelines")
+ ] = None
+ tone: Annotated[
+ Optional[str],
+ Field(
+ description="Brand voice and messaging tone (e.g., 'professional', 'casual', 'humorous', 'trustworthy', 'innovative')"
+ ),
+ ] = None
+ tagline: Annotated[Optional[str], Field(description="Brand tagline or slogan")] = (
+ None
+ )
+ assets: Annotated[
+ Optional[list[Asset14]],
+ Field(
+ description="Brand asset library with explicit assets and tags. Assets are referenced inline with URLs pointing to CDN-hosted files."
+ ),
+ ] = None
+ product_catalog: Annotated[
+ Optional[ProductCatalog11],
+ Field(
+ description="Product catalog information for e-commerce advertisers. Enables SKU-level creative generation and product selection."
+ ),
+ ] = None
+ disclaimers: Annotated[
+ Optional[list[Disclaimer]],
+ Field(
+ description="Legal disclaimers or required text that must appear in creatives"
+ ),
+ ] = None
+ industry: Annotated[
+ Optional[str],
+ Field(
+ description="Industry or vertical (e.g., 'retail', 'automotive', 'finance', 'healthcare')"
+ ),
+ ] = None
+ target_audience: Annotated[
+ Optional[str], Field(description="Primary target audience description")
+ ] = None
+ contact: Annotated[
+ Optional[Contact], Field(description="Brand contact information")
+ ] = None
+ metadata: Annotated[
+ Optional[Metadata], Field(description="Additional brand metadata")
+ ] = None
+
+
+class Filters1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ delivery_type: Annotated[
+ Optional[DeliveryType],
+ Field(description="Type of inventory delivery", title="Delivery Type"),
+ ] = None
+ is_fixed_price: Annotated[
+ Optional[bool], Field(description="Filter for fixed price vs auction products")
+ ] = None
+ format_types: Annotated[
+ Optional[list[FormatType]], Field(description="Filter by format types")
+ ] = None
+ format_ids: Annotated[
+ Optional[list[str]], Field(description="Filter by specific format IDs")
+ ] = None
+ standard_formats_only: Annotated[
+ Optional[bool],
+ Field(description="Only return products accepting IAB standard formats"),
+ ] = None
+ min_exposures: Annotated[
+ Optional[int],
+ Field(
+ description="Minimum exposures/impressions needed for measurement validity",
+ ge=1,
+ ),
+ ] = None
+
+
+class GetProductsRequest2(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ Optional[str],
+ Field(
+ description="AdCP schema version for this request",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ] = "1.6.0"
+ brief: Annotated[
+ Optional[str],
+ Field(description="Natural language description of campaign requirements"),
+ ] = None
+ promoted_offering: Annotated[
+ Optional[str],
+ Field(
+ description="DEPRECATED: Use brand_manifest instead. Legacy field for describing what is being promoted."
+ ),
+ ] = None
+ brand_manifest: Annotated[
+ Union[Union[BrandManifest9, BrandManifest10], AnyUrl],
+ Field(
+ description="Brand manifest provided either as an inline object or a URL string pointing to a hosted manifest",
+ examples=[
+ {
+ "description": "Inline brand manifest",
+ "data": {
+ "url": "https://acmecorp.com",
+ "name": "ACME Corporation",
+ "colors": {"primary": "#FF6B35"},
+ },
+ },
+ {
+ "description": "URL string reference to hosted manifest",
+ "data": "https://cdn.acmecorp.com/brand-manifest.json",
+ },
+ ],
+ title="Brand Manifest Reference",
+ ),
+ ]
+ filters: Annotated[
+ Optional[Filters1],
+ Field(description="Structured filters for product discovery"),
+ ] = None
+
+
+class GetProductsRequest(RootModel[Union[GetProductsRequest1, GetProductsRequest2]]):
+ root: Annotated[
+ Union[GetProductsRequest1, GetProductsRequest2],
+ Field(
+ description="Request parameters for discovering available advertising products",
+ title="Get Products Request",
+ ),
+ ]
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_products_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_products_response_json.py
new file mode 100644
index 0000000..4aabbbf
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_get_products_response_json.py
@@ -0,0 +1,1270 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_get-products-response_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Literal, Optional, Union
+
+from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, RootModel
+
+
+class Status(Enum):
+ submitted = "submitted"
+ working = "working"
+ input_required = "input-required"
+ completed = "completed"
+ canceled = "canceled"
+ failed = "failed"
+ rejected = "rejected"
+ auth_required = "auth-required"
+ unknown = "unknown"
+
+
+class PropertyType(Enum):
+ website = "website"
+ mobile_app = "mobile_app"
+ ctv_app = "ctv_app"
+ dooh = "dooh"
+ podcast = "podcast"
+ radio = "radio"
+ streaming_audio = "streaming_audio"
+
+
+class Identifier(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ type: Annotated[
+ str,
+ Field(
+ description="Type of identifier (e.g., 'domain', 'bundle_id', 'roku_store_id', 'podcast_guid')"
+ ),
+ ]
+ value: Annotated[
+ str,
+ Field(
+ description="The identifier value. For domain type: 'example.com' matches www.example.com and m.example.com only; 'subdomain.example.com' matches that specific subdomain; '*.example.com' matches all subdomains"
+ ),
+ ]
+
+
+class Tag(RootModel[str]):
+ root: Annotated[
+ str,
+ Field(
+ description="Lowercase tag with underscores (e.g., 'conde_nast_network', 'premium_content')",
+ pattern="^[a-z0-9_]+$",
+ ),
+ ]
+
+
+class Property(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ property_type: Annotated[
+ PropertyType, Field(description="Type of advertising property")
+ ]
+ name: Annotated[str, Field(description="Human-readable property name")]
+ identifiers: Annotated[
+ list[Identifier],
+ Field(description="Array of identifiers for this property", min_length=1),
+ ]
+ tags: Annotated[
+ Optional[list[Tag]],
+ Field(
+ description="Tags for categorization and grouping (e.g., network membership, content categories)"
+ ),
+ ] = None
+ publisher_domain: Annotated[
+ str,
+ Field(
+ description="Domain where adagents.json should be checked for authorization validation"
+ ),
+ ]
+
+
+class PropertyTag(RootModel[str]):
+ root: Annotated[
+ str,
+ Field(
+ description="Lowercase tag with underscores (e.g., 'local_radio', 'premium_content')",
+ pattern="^[a-z0-9_]+$",
+ ),
+ ]
+
+
+class DeliveryType(Enum):
+ guaranteed = "guaranteed"
+ non_guaranteed = "non_guaranteed"
+
+
+class PricingOptions(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPM rate (cost per 1,000 impressions)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PriceGuidance(BaseModel):
+ floor: Annotated[
+ float,
+ Field(
+ description="Minimum bid price - publisher will reject bids under this value",
+ ge=0.0,
+ ),
+ ]
+ p25: Annotated[
+ Optional[float], Field(description="25th percentile winning price", ge=0.0)
+ ] = None
+ p50: Annotated[
+ Optional[float], Field(description="Median winning price", ge=0.0)
+ ] = None
+ p75: Annotated[
+ Optional[float], Field(description="75th percentile winning price", ge=0.0)
+ ] = None
+ p90: Annotated[
+ Optional[float], Field(description="90th percentile winning price", ge=0.0)
+ ] = None
+
+
+class PricingOptions15(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_auction')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ price_guidance: Annotated[
+ PriceGuidance,
+ Field(description="Pricing guidance for auction-based CPM bidding"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions16(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpc_usd_fixed')"
+ ),
+ ]
+ pricing_model: Annotated[Literal["cpc"], Field(description="Cost per click")]
+ rate: Annotated[float, Field(description="Fixed CPC rate (cost per click)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions17(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpcv_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpcv"], Field(description="Cost per completed view (100% completion)")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPCV rate (cost per 100% completion)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class ViewThreshold(RootModel[float]):
+ root: Annotated[
+ float,
+ Field(
+ description="Percentage completion threshold for CPV pricing (0.0 to 1.0, e.g., 0.5 = 50% completion)",
+ ge=0.0,
+ le=1.0,
+ ),
+ ]
+
+
+class ViewThreshold7(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_seconds: Annotated[
+ int,
+ Field(
+ description="Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view')",
+ ge=1,
+ ),
+ ]
+
+
+class Parameters(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ view_threshold: Union[ViewThreshold, ViewThreshold7]
+
+
+class PricingOptions18(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpv_usd_50pct')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpv"], Field(description="Cost per view at threshold")
+ ]
+ rate: Annotated[float, Field(description="Fixed CPV rate (cost per view)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters,
+ Field(description="CPV-specific parameters defining the view threshold"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters10(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ demographic: Annotated[
+ str,
+ Field(
+ description="Target demographic in Nielsen format: P/M/W/A/C + age range. Examples: P18-49 (Persons 18-49), M25-54 (Men 25-54), W35+ (Women 35+), A18-34 (Adults 18-34), C2-11 (Children 2-11)",
+ pattern="^[PMWAC][0-9]{2}(-[0-9]{2}|\\+)$",
+ ),
+ ]
+ min_points: Annotated[
+ Optional[float],
+ Field(description="Minimum GRPs/TRPs required for this pricing option", ge=0.0),
+ ] = None
+
+
+class PricingOptions19(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpp_usd_p18-49')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpp"], Field(description="Cost per Gross Rating Point")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPP rate (cost per rating point)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters10,
+ Field(
+ description="CPP-specific parameters for demographic targeting and GRP requirements"
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters11(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_hours: Annotated[
+ Optional[float],
+ Field(
+ description="Duration in hours for time-based flat rate pricing (DOOH)",
+ ge=0.0,
+ ),
+ ] = None
+ sov_percentage: Annotated[
+ Optional[float],
+ Field(
+ description="Guaranteed share of voice as percentage (DOOH, 0-100)",
+ ge=0.0,
+ le=100.0,
+ ),
+ ] = None
+ loop_duration_seconds: Annotated[
+ Optional[int],
+ Field(description="Duration of ad loop rotation in seconds (DOOH)", ge=1),
+ ] = None
+ min_plays_per_hour: Annotated[
+ Optional[int],
+ Field(
+ description="Minimum number of times ad plays per hour (DOOH frequency guarantee)",
+ ge=0,
+ ),
+ ] = None
+ venue_package: Annotated[
+ Optional[str],
+ Field(
+ description="Named venue package identifier for DOOH (e.g., 'times_square_network', 'airport_terminals')"
+ ),
+ ] = None
+ estimated_impressions: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated impressions for this flat rate option (informational, commonly used with SOV or time-based DOOH)",
+ ge=0,
+ ),
+ ] = None
+ daypart: Annotated[
+ Optional[str],
+ Field(
+ description="Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')"
+ ),
+ ] = None
+
+
+class PricingOptions20(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'flat_rate_usd_24h_takeover')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["flat_rate"],
+ Field(description="Fixed cost regardless of delivery volume"),
+ ]
+ rate: Annotated[float, Field(description="Flat rate cost", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ is_fixed: Annotated[
+ Literal[True],
+ Field(
+ description="Whether this is a fixed rate (true) or auction-based (false)"
+ ),
+ ]
+ parameters: Annotated[
+ Optional[Parameters11],
+ Field(description="Flat rate parameters for DOOH and time-based campaigns"),
+ ] = None
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Measurement(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ type: Annotated[
+ str,
+ Field(
+ description="Type of measurement",
+ examples=["incremental_sales_lift", "brand_lift", "foot_traffic"],
+ ),
+ ]
+ attribution: Annotated[
+ str,
+ Field(
+ description="Attribution methodology",
+ examples=["deterministic_purchase", "probabilistic"],
+ ),
+ ]
+ window: Annotated[
+ Optional[str],
+ Field(description="Attribution window", examples=["30_days", "7_days"]),
+ ] = None
+ reporting: Annotated[
+ str,
+ Field(
+ description="Reporting frequency and format",
+ examples=["weekly_dashboard", "real_time_api"],
+ ),
+ ]
+
+
+class DeliveryMeasurement(BaseModel):
+ provider: Annotated[
+ str,
+ Field(
+ description="Measurement provider(s) used for this product (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions')"
+ ),
+ ]
+ notes: Annotated[
+ Optional[str],
+ Field(
+ description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly')"
+ ),
+ ] = None
+
+
+class AvailableReportingFrequency(Enum):
+ hourly = "hourly"
+ daily = "daily"
+ monthly = "monthly"
+
+
+class AvailableMetric(Enum):
+ impressions = "impressions"
+ spend = "spend"
+ clicks = "clicks"
+ ctr = "ctr"
+ video_completions = "video_completions"
+ completion_rate = "completion_rate"
+ conversions = "conversions"
+ viewability = "viewability"
+ engagement_rate = "engagement_rate"
+
+
+class ReportingCapabilities(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ available_reporting_frequencies: Annotated[
+ list[AvailableReportingFrequency],
+ Field(description="Supported reporting frequency options", min_length=1),
+ ]
+ expected_delay_minutes: Annotated[
+ int,
+ Field(
+ description="Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)",
+ examples=[240, 300, 1440],
+ ge=0,
+ ),
+ ]
+ timezone: Annotated[
+ str,
+ Field(
+ description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.",
+ examples=[
+ "UTC",
+ "America/New_York",
+ "Europe/London",
+ "America/Los_Angeles",
+ ],
+ ),
+ ]
+ supports_webhooks: Annotated[
+ bool,
+ Field(
+ description="Whether this product supports webhook-based reporting notifications"
+ ),
+ ]
+ available_metrics: Annotated[
+ list[AvailableMetric],
+ Field(
+ description="Metrics available in reporting. Impressions and spend are always implicitly included.",
+ examples=[
+ ["impressions", "spend", "clicks", "video_completions"],
+ ["impressions", "spend", "conversions"],
+ ],
+ ),
+ ]
+
+
+class CoBranding(Enum):
+ required = "required"
+ optional = "optional"
+ none = "none"
+
+
+class LandingPage(Enum):
+ any = "any"
+ retailer_site_only = "retailer_site_only"
+ must_include_retailer = "must_include_retailer"
+
+
+class CreativePolicy(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ co_branding: Annotated[CoBranding, Field(description="Co-branding requirement")]
+ landing_page: Annotated[LandingPage, Field(description="Landing page requirements")]
+ templates_available: Annotated[
+ bool, Field(description="Whether creative templates are provided")
+ ]
+
+
+class Products(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ product_id: Annotated[str, Field(description="Unique identifier for the product")]
+ name: Annotated[str, Field(description="Human-readable product name")]
+ description: Annotated[
+ str, Field(description="Detailed description of the product and its inventory")
+ ]
+ properties: Annotated[
+ list[Property],
+ Field(
+ description="Array of advertising properties covered by this product for adagents.json validation",
+ min_length=1,
+ ),
+ ]
+ property_tags: Annotated[
+ Optional[list[PropertyTag]],
+ Field(
+ description="Tags identifying groups of properties covered by this product (use list_authorized_properties to get full property details)",
+ min_length=1,
+ ),
+ ] = None
+ format_ids: Annotated[
+ list[str],
+ Field(
+ description="Array of supported creative format IDs - use list_creative_formats to get full format details"
+ ),
+ ]
+ delivery_type: Annotated[
+ DeliveryType,
+ Field(description="Type of inventory delivery", title="Delivery Type"),
+ ]
+ pricing_options: Annotated[
+ list[
+ Union[
+ PricingOptions,
+ PricingOptions15,
+ PricingOptions16,
+ PricingOptions17,
+ PricingOptions18,
+ PricingOptions19,
+ PricingOptions20,
+ ]
+ ],
+ Field(description="Available pricing models for this product", min_length=1),
+ ]
+ estimated_exposures: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated exposures/impressions for guaranteed products", ge=0
+ ),
+ ] = None
+ measurement: Annotated[
+ Optional[Measurement],
+ Field(
+ description="Measurement capabilities included with a product",
+ title="Measurement",
+ ),
+ ] = None
+ delivery_measurement: Annotated[
+ DeliveryMeasurement,
+ Field(
+ description="Measurement provider and methodology for delivery metrics. The buyer accepts the declared provider as the source of truth for the buy. REQUIRED for all products."
+ ),
+ ]
+ reporting_capabilities: Annotated[
+ Optional[ReportingCapabilities],
+ Field(
+ description="Reporting capabilities available for a product",
+ title="Reporting Capabilities",
+ ),
+ ] = None
+ creative_policy: Annotated[
+ Optional[CreativePolicy],
+ Field(
+ description="Creative requirements and restrictions for a product",
+ title="Creative Policy",
+ ),
+ ] = None
+ is_custom: Annotated[
+ Optional[bool], Field(description="Whether this is a custom product")
+ ] = None
+ brief_relevance: Annotated[
+ Optional[str],
+ Field(
+ description="Explanation of why this product matches the brief (only included when brief is provided)"
+ ),
+ ] = None
+ expires_at: Annotated[
+ Optional[AwareDatetime],
+ Field(description="Expiration timestamp for custom products"),
+ ] = None
+
+
+class Property3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ property_type: Annotated[
+ PropertyType, Field(description="Type of advertising property")
+ ]
+ name: Annotated[str, Field(description="Human-readable property name")]
+ identifiers: Annotated[
+ list[Identifier],
+ Field(description="Array of identifiers for this property", min_length=1),
+ ]
+ tags: Annotated[
+ Optional[list[Tag]],
+ Field(
+ description="Tags for categorization and grouping (e.g., network membership, content categories)"
+ ),
+ ] = None
+ publisher_domain: Annotated[
+ str,
+ Field(
+ description="Domain where adagents.json should be checked for authorization validation"
+ ),
+ ]
+
+
+class PricingOptions21(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPM rate (cost per 1,000 impressions)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions22(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpm_usd_auction')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpm"], Field(description="Cost per 1,000 impressions")
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ price_guidance: Annotated[
+ PriceGuidance,
+ Field(description="Pricing guidance for auction-based CPM bidding"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions23(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpc_usd_fixed')"
+ ),
+ ]
+ pricing_model: Annotated[Literal["cpc"], Field(description="Cost per click")]
+ rate: Annotated[float, Field(description="Fixed CPC rate (cost per click)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class PricingOptions24(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpcv_usd_guaranteed')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpcv"], Field(description="Cost per completed view (100% completion)")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPCV rate (cost per 100% completion)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class ViewThreshold8(RootModel[float]):
+ root: Annotated[
+ float,
+ Field(
+ description="Percentage completion threshold for CPV pricing (0.0 to 1.0, e.g., 0.5 = 50% completion)",
+ ge=0.0,
+ le=1.0,
+ ),
+ ]
+
+
+class ViewThreshold9(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_seconds: Annotated[
+ int,
+ Field(
+ description="Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view')",
+ ge=1,
+ ),
+ ]
+
+
+class Parameters12(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ view_threshold: Union[ViewThreshold8, ViewThreshold9]
+
+
+class PricingOptions25(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpv_usd_50pct')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpv"], Field(description="Cost per view at threshold")
+ ]
+ rate: Annotated[float, Field(description="Fixed CPV rate (cost per view)", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters12,
+ Field(description="CPV-specific parameters defining the view threshold"),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters13(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ demographic: Annotated[
+ str,
+ Field(
+ description="Target demographic in Nielsen format: P/M/W/A/C + age range. Examples: P18-49 (Persons 18-49), M25-54 (Men 25-54), W35+ (Women 35+), A18-34 (Adults 18-34), C2-11 (Children 2-11)",
+ pattern="^[PMWAC][0-9]{2}(-[0-9]{2}|\\+)$",
+ ),
+ ]
+ min_points: Annotated[
+ Optional[float],
+ Field(description="Minimum GRPs/TRPs required for this pricing option", ge=0.0),
+ ] = None
+
+
+class PricingOptions26(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'cpp_usd_p18-49')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["cpp"], Field(description="Cost per Gross Rating Point")
+ ]
+ rate: Annotated[
+ float, Field(description="Fixed CPP rate (cost per rating point)", ge=0.0)
+ ]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ parameters: Annotated[
+ Parameters13,
+ Field(
+ description="CPP-specific parameters for demographic targeting and GRP requirements"
+ ),
+ ]
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class Parameters14(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ duration_hours: Annotated[
+ Optional[float],
+ Field(
+ description="Duration in hours for time-based flat rate pricing (DOOH)",
+ ge=0.0,
+ ),
+ ] = None
+ sov_percentage: Annotated[
+ Optional[float],
+ Field(
+ description="Guaranteed share of voice as percentage (DOOH, 0-100)",
+ ge=0.0,
+ le=100.0,
+ ),
+ ] = None
+ loop_duration_seconds: Annotated[
+ Optional[int],
+ Field(description="Duration of ad loop rotation in seconds (DOOH)", ge=1),
+ ] = None
+ min_plays_per_hour: Annotated[
+ Optional[int],
+ Field(
+ description="Minimum number of times ad plays per hour (DOOH frequency guarantee)",
+ ge=0,
+ ),
+ ] = None
+ venue_package: Annotated[
+ Optional[str],
+ Field(
+ description="Named venue package identifier for DOOH (e.g., 'times_square_network', 'airport_terminals')"
+ ),
+ ] = None
+ estimated_impressions: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated impressions for this flat rate option (informational, commonly used with SOV or time-based DOOH)",
+ ge=0,
+ ),
+ ] = None
+ daypart: Annotated[
+ Optional[str],
+ Field(
+ description="Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')"
+ ),
+ ] = None
+
+
+class PricingOptions27(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ pricing_option_id: Annotated[
+ str,
+ Field(
+ description="Unique identifier for this pricing option within the product (e.g., 'flat_rate_usd_24h_takeover')"
+ ),
+ ]
+ pricing_model: Annotated[
+ Literal["flat_rate"],
+ Field(description="Fixed cost regardless of delivery volume"),
+ ]
+ rate: Annotated[float, Field(description="Flat rate cost", ge=0.0)]
+ currency: Annotated[
+ str,
+ Field(
+ description="ISO 4217 currency code",
+ examples=["USD", "EUR", "GBP", "JPY"],
+ pattern="^[A-Z]{3}$",
+ ),
+ ]
+ is_fixed: Annotated[
+ Literal[True],
+ Field(
+ description="Whether this is a fixed rate (true) or auction-based (false)"
+ ),
+ ]
+ parameters: Annotated[
+ Optional[Parameters14],
+ Field(description="Flat rate parameters for DOOH and time-based campaigns"),
+ ] = None
+ min_spend_per_package: Annotated[
+ Optional[float],
+ Field(
+ description="Minimum spend requirement per package using this pricing option, in the specified currency",
+ ge=0.0,
+ ),
+ ] = None
+
+
+class ReportingCapabilities3(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ available_reporting_frequencies: Annotated[
+ list[AvailableReportingFrequency],
+ Field(description="Supported reporting frequency options", min_length=1),
+ ]
+ expected_delay_minutes: Annotated[
+ int,
+ Field(
+ description="Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)",
+ examples=[240, 300, 1440],
+ ge=0,
+ ),
+ ]
+ timezone: Annotated[
+ str,
+ Field(
+ description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.",
+ examples=[
+ "UTC",
+ "America/New_York",
+ "Europe/London",
+ "America/Los_Angeles",
+ ],
+ ),
+ ]
+ supports_webhooks: Annotated[
+ bool,
+ Field(
+ description="Whether this product supports webhook-based reporting notifications"
+ ),
+ ]
+ available_metrics: Annotated[
+ list[AvailableMetric],
+ Field(
+ description="Metrics available in reporting. Impressions and spend are always implicitly included.",
+ examples=[
+ ["impressions", "spend", "clicks", "video_completions"],
+ ["impressions", "spend", "conversions"],
+ ],
+ ),
+ ]
+
+
+class CreativePolicy4(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ co_branding: Annotated[CoBranding, Field(description="Co-branding requirement")]
+ landing_page: Annotated[LandingPage, Field(description="Landing page requirements")]
+ templates_available: Annotated[
+ bool, Field(description="Whether creative templates are provided")
+ ]
+
+
+class Products1(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ product_id: Annotated[str, Field(description="Unique identifier for the product")]
+ name: Annotated[str, Field(description="Human-readable product name")]
+ description: Annotated[
+ str, Field(description="Detailed description of the product and its inventory")
+ ]
+ properties: Annotated[
+ Optional[list[Property3]],
+ Field(
+ description="Array of advertising properties covered by this product for adagents.json validation",
+ min_length=1,
+ ),
+ ] = None
+ property_tags: Annotated[
+ list[PropertyTag],
+ Field(
+ description="Tags identifying groups of properties covered by this product (use list_authorized_properties to get full property details)",
+ min_length=1,
+ ),
+ ]
+ format_ids: Annotated[
+ list[str],
+ Field(
+ description="Array of supported creative format IDs - use list_creative_formats to get full format details"
+ ),
+ ]
+ delivery_type: Annotated[
+ DeliveryType,
+ Field(description="Type of inventory delivery", title="Delivery Type"),
+ ]
+ pricing_options: Annotated[
+ list[
+ Union[
+ PricingOptions21,
+ PricingOptions22,
+ PricingOptions23,
+ PricingOptions24,
+ PricingOptions25,
+ PricingOptions26,
+ PricingOptions27,
+ ]
+ ],
+ Field(description="Available pricing models for this product", min_length=1),
+ ]
+ estimated_exposures: Annotated[
+ Optional[int],
+ Field(
+ description="Estimated exposures/impressions for guaranteed products", ge=0
+ ),
+ ] = None
+ measurement: Annotated[
+ Optional[Measurement],
+ Field(
+ description="Measurement capabilities included with a product",
+ title="Measurement",
+ ),
+ ] = None
+ delivery_measurement: Annotated[
+ DeliveryMeasurement,
+ Field(
+ description="Measurement provider and methodology for delivery metrics. The buyer accepts the declared provider as the source of truth for the buy. REQUIRED for all products."
+ ),
+ ]
+ reporting_capabilities: Annotated[
+ Optional[ReportingCapabilities3],
+ Field(
+ description="Reporting capabilities available for a product",
+ title="Reporting Capabilities",
+ ),
+ ] = None
+ creative_policy: Annotated[
+ Optional[CreativePolicy4],
+ Field(
+ description="Creative requirements and restrictions for a product",
+ title="Creative Policy",
+ ),
+ ] = None
+ is_custom: Annotated[
+ Optional[bool], Field(description="Whether this is a custom product")
+ ] = None
+ brief_relevance: Annotated[
+ Optional[str],
+ Field(
+ description="Explanation of why this product matches the brief (only included when brief is provided)"
+ ),
+ ] = None
+ expires_at: Annotated[
+ Optional[AwareDatetime],
+ Field(description="Expiration timestamp for custom products"),
+ ] = None
+
+
+class Error(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ code: Annotated[str, Field(description="Error code for programmatic handling")]
+ message: Annotated[str, Field(description="Human-readable error message")]
+ field: Annotated[
+ Optional[str],
+ Field(
+ description="Field path associated with the error (e.g., 'packages[0].targeting')"
+ ),
+ ] = None
+ suggestion: Annotated[
+ Optional[str], Field(description="Suggested fix for the error")
+ ] = None
+ retry_after: Annotated[
+ Optional[float],
+ Field(description="Seconds to wait before retrying the operation", ge=0.0),
+ ] = None
+ details: Annotated[
+ Optional[Any], Field(description="Additional task-specific error details")
+ ] = None
+
+
+class GetProductsResponse(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ str,
+ Field(
+ description="AdCP schema version used for this response",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ]
+ status: Annotated[
+ Optional[Status],
+ Field(
+ description="Standardized task status values based on A2A TaskState enum. Indicates the current state of any AdCP operation.",
+ title="Task Status",
+ ),
+ ] = "completed"
+ products: Annotated[
+ list[Union[Products, Products1]],
+ Field(description="Array of matching products"),
+ ]
+ errors: Annotated[
+ Optional[list[Error]],
+ Field(
+ description="Task-specific errors and warnings (e.g., product filtering issues)"
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_authorized_properties_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_authorized_properties_request_json.py
new file mode 100644
index 0000000..43d4d14
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_authorized_properties_request_json.py
@@ -0,0 +1,35 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_list-authorized-properties-request_json.json
+
+from __future__ import annotations
+
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field, RootModel
+
+
+class Tag(RootModel[str]):
+ root: Annotated[
+ str,
+ Field(
+ description="Tag to filter by (e.g., 'local_radio', 'premium_content')",
+ pattern="^[a-z0-9_]+$",
+ ),
+ ]
+
+
+class ListAuthorizedPropertiesRequest(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ Optional[str],
+ Field(
+ description="AdCP schema version for this request",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ] = "1.0.0"
+ tags: Annotated[
+ Optional[list[Tag]],
+ Field(description="Filter properties by specific tags (optional)"),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_authorized_properties_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_authorized_properties_response_json.py
new file mode 100644
index 0000000..049587e
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_authorized_properties_response_json.py
@@ -0,0 +1,174 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_list-authorized-properties-response_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional
+
+from pydantic import BaseModel, ConfigDict, Field, RootModel
+
+
+class PropertyType(Enum):
+ website = "website"
+ mobile_app = "mobile_app"
+ ctv_app = "ctv_app"
+ dooh = "dooh"
+ podcast = "podcast"
+ radio = "radio"
+ streaming_audio = "streaming_audio"
+
+
+class Identifier(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ type: Annotated[
+ str,
+ Field(
+ description="Type of identifier (e.g., 'domain', 'bundle_id', 'roku_store_id', 'podcast_guid')"
+ ),
+ ]
+ value: Annotated[
+ str,
+ Field(
+ description="The identifier value. For domain type: 'example.com' matches www.example.com and m.example.com only; 'subdomain.example.com' matches that specific subdomain; '*.example.com' matches all subdomains"
+ ),
+ ]
+
+
+class Tag(RootModel[str]):
+ root: Annotated[
+ str,
+ Field(
+ description="Lowercase tag with underscores (e.g., 'conde_nast_network', 'premium_content')",
+ pattern="^[a-z0-9_]+$",
+ ),
+ ]
+
+
+class Property(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ property_type: Annotated[
+ PropertyType, Field(description="Type of advertising property")
+ ]
+ name: Annotated[str, Field(description="Human-readable property name")]
+ identifiers: Annotated[
+ list[Identifier],
+ Field(description="Array of identifiers for this property", min_length=1),
+ ]
+ tags: Annotated[
+ Optional[list[Tag]],
+ Field(
+ description="Tags for categorization and grouping (e.g., network membership, content categories)"
+ ),
+ ] = None
+ publisher_domain: Annotated[
+ str,
+ Field(
+ description="Domain where adagents.json should be checked for authorization validation"
+ ),
+ ]
+
+
+class Tags(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ name: Annotated[str, Field(description="Human-readable name for this tag")]
+ description: Annotated[
+ str, Field(description="Description of what this tag represents")
+ ]
+
+
+class PrimaryChannel(Enum):
+ display = "display"
+ video = "video"
+ audio = "audio"
+ native = "native"
+ dooh = "dooh"
+ ctv = "ctv"
+ podcast = "podcast"
+ retail = "retail"
+ social = "social"
+
+
+class PrimaryCountry(RootModel[str]):
+ root: Annotated[str, Field(pattern="^[A-Z]{2}$")]
+
+
+class Error(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ code: Annotated[str, Field(description="Error code for programmatic handling")]
+ message: Annotated[str, Field(description="Human-readable error message")]
+ field: Annotated[
+ Optional[str],
+ Field(
+ description="Field path associated with the error (e.g., 'packages[0].targeting')"
+ ),
+ ] = None
+ suggestion: Annotated[
+ Optional[str], Field(description="Suggested fix for the error")
+ ] = None
+ retry_after: Annotated[
+ Optional[float],
+ Field(description="Seconds to wait before retrying the operation", ge=0.0),
+ ] = None
+ details: Annotated[
+ Optional[Any], Field(description="Additional task-specific error details")
+ ] = None
+
+
+class ListAuthorizedPropertiesResponse(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ str,
+ Field(
+ description="AdCP schema version used for this response",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ]
+ properties: Annotated[
+ list[Property],
+ Field(
+ description="Array of all properties this agent is authorized to represent"
+ ),
+ ]
+ tags: Annotated[
+ Optional[dict[str, Tags]],
+ Field(description="Metadata for each tag referenced by properties"),
+ ] = None
+ primary_channels: Annotated[
+ Optional[list[PrimaryChannel]],
+ Field(
+ description="Primary advertising channels represented in this property portfolio. Helps buying agents quickly filter relevance.",
+ min_length=1,
+ ),
+ ] = None
+ primary_countries: Annotated[
+ Optional[list[PrimaryCountry]],
+ Field(
+ description="Primary countries (ISO 3166-1 alpha-2 codes) where properties are concentrated. Helps buying agents quickly filter relevance.",
+ min_length=1,
+ ),
+ ] = None
+ portfolio_description: Annotated[
+ Optional[str],
+ Field(
+ description="Markdown-formatted description of the property portfolio, including inventory types, audience characteristics, and special features.",
+ max_length=5000,
+ min_length=1,
+ ),
+ ] = None
+ errors: Annotated[
+ Optional[list[Error]],
+ Field(
+ description="Task-specific errors and warnings (e.g., property availability issues)"
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creative_formats_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creative_formats_request_json.py
new file mode 100644
index 0000000..058b22a
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creative_formats_request_json.py
@@ -0,0 +1,69 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_list-creative-formats-request_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Optional
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class Type(Enum):
+ audio = "audio"
+ video = "video"
+ display = "display"
+ dooh = "dooh"
+
+
+class AssetType(Enum):
+ image = "image"
+ video = "video"
+ audio = "audio"
+ text = "text"
+ html = "html"
+ javascript = "javascript"
+ url = "url"
+
+
+class ListCreativeFormatsRequest(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ adcp_version: Annotated[
+ Optional[str],
+ Field(
+ description="AdCP schema version for this request",
+ pattern="^\\d+\\.\\d+\\.\\d+$",
+ ),
+ ] = "1.6.0"
+ format_ids: Annotated[
+ Optional[list[str]],
+ Field(
+ description="Return only these specific format IDs (e.g., from get_products response)"
+ ),
+ ] = None
+ type: Annotated[
+ Optional[Type],
+ Field(
+ description="Filter by format type (technical categories with distinct requirements)"
+ ),
+ ] = None
+ asset_types: Annotated[
+ Optional[list[AssetType]],
+ Field(
+ description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags."
+ ),
+ ] = None
+ dimensions: Annotated[
+ Optional[str],
+ Field(
+ description="Filter to formats with specific dimensions (e.g., '300x250', '728x90'). Useful with asset_types to find specific sizes like '300x250 JavaScript'"
+ ),
+ ] = None
+ name_search: Annotated[
+ Optional[str],
+ Field(
+ description="Search for formats by name (case-insensitive partial match)"
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creative_formats_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creative_formats_response_json.py
new file mode 100644
index 0000000..173aa67
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creative_formats_response_json.py
@@ -0,0 +1,271 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_list-creative-formats-response_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional, Union
+
+from pydantic import AnyUrl, BaseModel, ConfigDict, Field
+
+
+class Status(Enum):
+ submitted = "submitted"
+ working = "working"
+ input_required = "input-required"
+ completed = "completed"
+ canceled = "canceled"
+ failed = "failed"
+ rejected = "rejected"
+ auth_required = "auth-required"
+ unknown = "unknown"
+
+
+class Type(Enum):
+ audio = "audio"
+ video = "video"
+ display = "display"
+ native = "native"
+ dooh = "dooh"
+ rich_media = "rich_media"
+ universal = "universal"
+
+
+class Category(Enum):
+ standard = "standard"
+ custom = "custom"
+
+
+class AssetType(Enum):
+ image = "image"
+ video = "video"
+ audio = "audio"
+ text = "text"
+ html = "html"
+ javascript = "javascript"
+ url = "url"
+ brand_manifest = "brand_manifest"
+
+
+class AssetsRequired(BaseModel):
+ asset_id: Annotated[
+ str, Field(description="Identifier for this asset in the format")
+ ]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ asset_role: Annotated[
+ Optional[str],
+ Field(
+ description="Purpose of this asset (e.g., 'hero_image', 'logo', 'headline', 'cta_button')"
+ ),
+ ] = None
+ required: Annotated[
+ Optional[bool], Field(description="Whether this asset is required")
+ ] = None
+ requirements: Annotated[
+ Optional[dict[str, Any]],
+ Field(
+ description="Technical requirements for this asset (dimensions, file size, duration, etc.)"
+ ),
+ ] = None
+
+
+class Repeatable(Enum):
+ boolean_true = True
+
+
+class Asset(BaseModel):
+ asset_id: Annotated[
+ str, Field(description="Identifier for this asset within the group")
+ ]
+ asset_type: Annotated[AssetType, Field(description="Type of asset")]
+ asset_role: Annotated[Optional[str], Field(description="Purpose of this asset")] = (
+ None
+ )
+ required: Annotated[
+ Optional[bool],
+ Field(description="Whether this asset is required in each repetition"),
+ ] = None
+ requirements: Annotated[
+ Optional[dict[str, Any]],
+ Field(description="Technical requirements for this asset"),
+ ] = None
+
+
+class AssetsRequired3(BaseModel):
+ asset_group_id: Annotated[
+ str,
+ Field(
+ description="Identifier for this asset group (e.g., 'product', 'slide', 'card')"
+ ),
+ ]
+ repeatable: Annotated[
+ Repeatable, Field(description="Indicates this is a repeatable asset group")
+ ]
+ min_count: Annotated[
+ int, Field(description="Minimum number of repetitions required", ge=1)
+ ]
+ max_count: Annotated[
+ int, Field(description="Maximum number of repetitions allowed", ge=1)
+ ]
+ assets: Annotated[
+ list[Asset], Field(description="Assets within each repetition of this group")
+ ]
+
+
+class Format(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ format_id: Annotated[str, Field(description="Unique identifier for the format")]
+ agent_url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Base URL of the agent that provides this format (authoritative source). E.g., 'https://reference.adcp.org', 'https://dco.example.com'"
+ ),
+ ] = None
+ name: Annotated[str, Field(description="Human-readable format name")]
+ description: Annotated[
+ Optional[str],
+ Field(
+ description="Plain text explanation of what this format does and what assets it requires"
+ ),
+ ] = None
+ preview_image: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Optional preview image URL for format browsing/discovery UI"
+ ),
+ ] = None
+ example_url: Annotated[
+ Optional[AnyUrl],
+ Field(
+ description="Optional URL to showcase page with examples and interactive demos of this format"
+ ),
+ ] = None
+ type: Annotated[
+ Type,
+ Field(
+ description="Media type of this format - determines rendering method and asset requirements"
+ ),
+ ]
+ category: Annotated[Optional[Category], Field(description="Format category")] = None
+ is_standard: Annotated[
+ Optional[bool],
+ Field(
+ description="Whether this format follows IAB specifications or AdCP standard format definitions (found in /schemas/v1/standard-formats/)"
+ ),
+ ] = None
+ requirements: Annotated[
+ Optional[dict[str, Any]],
+ Field(
+ description="Technical specifications for this format (e.g., dimensions, duration, file size limits, codecs)"
+ ),
+ ] = None
+ assets_required: Annotated[
+ Optional[list[Union[AssetsRequired, AssetsRequired3]]],
+ Field(
+ description="Array of required assets or asset groups for this format. Can contain individual assets or repeatable asset sequences (e.g., carousel products, slideshow frames)."
+ ),
+ ] = None
+ delivery: Annotated[
+ Optional[dict[str, Any]],
+ Field(
+ description="Delivery method specifications (e.g., hosted, VAST, third-party tags)"
+ ),
+ ] = None
+ accepts_3p_tags: Annotated[
+ Optional[bool],
+ Field(
+ description="Whether this format can accept third-party served creative tags as an alternative to hosted assets"
+ ),
+ ] = None
+ supported_macros: Annotated[
+ Optional[list[str]],
+ Field(
+ description="List of universal macros supported by this format (e.g., MEDIA_BUY_ID, CACHEBUSTER, DEVICE_ID). Used for validation and developer tooling."
+ ),
+ ] = None
+ output_format_ids: Annotated[
+ Optional[list[str]],
+ Field(
+ description="For generative formats: array of format IDs that this format can generate. When a format accepts inputs like brand_manifest and message, this specifies what concrete output formats can be produced (e.g., a generative banner format might output standard image banner formats)."
+ ),
+ ] = None
+
+
+class Capability(Enum):
+ validation = "validation"
+ assembly = "assembly"
+ generation = "generation"
+ preview = "preview"
+
+
+class CreativeAgent(BaseModel):
+ agent_url: Annotated[
+ AnyUrl,
+ Field(
+ description="Base URL for the creative agent (e.g., 'https://reference.adcp.org', 'https://dco.example.com'). Call list_creative_formats on this URL to get its formats."
+ ),
+ ]
+ agent_name: Annotated[
+ Optional[str], Field(description="Human-readable name for the creative agent")
+ ] = None
+ capabilities: Annotated[
+ Optional[list[Capability]],
+ Field(description="Capabilities this creative agent provides"),
+ ] = None
+
+
+class Error(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ code: Annotated[str, Field(description="Error code for programmatic handling")]
+ message: Annotated[str, Field(description="Human-readable error message")]
+ field: Annotated[
+ Optional[str],
+ Field(
+ description="Field path associated with the error (e.g., 'packages[0].targeting')"
+ ),
+ ] = None
+ suggestion: Annotated[
+ Optional[str], Field(description="Suggested fix for the error")
+ ] = None
+ retry_after: Annotated[
+ Optional[float],
+ Field(description="Seconds to wait before retrying the operation", ge=0.0),
+ ] = None
+ details: Annotated[
+ Optional[Any], Field(description="Additional task-specific error details")
+ ] = None
+
+
+class ListCreativeFormatsResponse(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ status: Annotated[
+ Optional[Status],
+ Field(
+ description="Standardized task status values based on A2A TaskState enum. Indicates the current state of any AdCP operation.",
+ title="Task Status",
+ ),
+ ] = None
+ formats: Annotated[
+ list[Format],
+ Field(
+ description="Full format definitions for all formats this agent supports. Each format's authoritative source is indicated by its agent_url field."
+ ),
+ ]
+ creative_agents: Annotated[
+ Optional[list[CreativeAgent]],
+ Field(
+ description="Optional: Creative agents that provide additional formats. Buyers can recursively query these agents to discover more formats. No authentication required for list_creative_formats."
+ ),
+ ] = None
+ errors: Annotated[
+ Optional[list[Error]],
+ Field(
+ description="Task-specific errors and warnings (e.g., format availability issues)"
+ ),
+ ] = None
diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creatives_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creatives_request_json.py
new file mode 100644
index 0000000..032e1da
--- /dev/null
+++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creatives_request_json.py
@@ -0,0 +1,231 @@
+# generated by datamodel-codegen:
+# filename: _schemas_v1_media-buy_list-creatives-request_json.json
+
+from __future__ import annotations
+
+from enum import Enum
+from typing import Annotated, Any, Optional
+
+from pydantic import AwareDatetime, BaseModel, ConfigDict, Field
+
+
+class Status(Enum):
+ processing = "processing"
+ approved = "approved"
+ rejected = "rejected"
+ pending_review = "pending_review"
+
+
+class SnippetType(Enum):
+ vast_xml = "vast_xml"
+ vast_url = "vast_url"
+ html = "html"
+ javascript = "javascript"
+ iframe = "iframe"
+ daast_url = "daast_url"
+
+
+class Filters(BaseModel):
+ model_config = ConfigDict(
+ extra="forbid",
+ )
+ format: Annotated[
+ Optional[str],
+ Field(
+ description="Filter by creative format type (e.g., video, audio, display)"
+ ),
+ ] = None
+ formats: Annotated[
+ Optional[list[str]],
+ Field(description="Filter by multiple creative format types"),
+ ] = None
+ status: Annotated[
+ Optional[Status],
+ Field(description="Status of a creative asset", title="Creative Status"),
+ ] = None
+ statuses: Annotated[
+ Optional[list[Any]], Field(description="Filter by multiple creative statuses")
+ ] = None
+ tags: Annotated[
+ Optional[list[str]],
+ Field(description="Filter by creative tags (all tags must match)"),
+ ] = None
+ tags_any: Annotated[
+ Optional[list[str]],
+ Field(description="Filter by creative tags (any tag must match)"),
+ ] = None
+ name_contains: Annotated[
+ Optional[str],
+ Field(
+ description="Filter by creative names containing this text (case-insensitive)"
+ ),
+ ] = None
+ creative_ids: Annotated[
+ Optional[list[str]],
+ Field(description="Filter by specific creative IDs", max_length=100),
+ ] = None
+ created_after: Annotated[
+ Optional[AwareDatetime],
+ Field(description="Filter creatives created after this date (ISO 8601)"),
+ ] = None
+ created_before: Annotated[
+ Optional[AwareDatetime],
+ Field(description="Filter creatives created before this date (ISO 8601)"),
+ ] = None
+ updated_after: Annotated[
+ Optional[AwareDatetime],
+ Field(description="Filter creatives last updated after this date (ISO 8601)"),
+ ] = None
+ updated_before: Annotated[
+ Optional[AwareDatetime],
+ Field(description="Filter creatives last updated before this date (ISO 8601)"),
+ ] = None
+ assigned_to_package: Annotated[
+ Optional[str],
+ Field(description="Filter creatives assigned to this specific package"),
+ ] = None
+ assigned_to_packages: Annotated[
+ Optional[list[str]],
+ Field(description="Filter creatives assigned to any of these packages"),
+ ] = None
+ unassigned: Annotated[
+ Optional[bool],
+ Field(
+ description="Filter for unassigned creatives when true, assigned creatives when false"
+ ),
+ ] = None
+ snippet_type: Annotated[
+ Optional[SnippetType],
+ Field(
+ description="Types of third-party creative snippets supported by AdCP",
+ examples=[
+ {
+ "type": "vast_xml",
+ "description": "Inline VAST XML",
+ "snippet": '

