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": 'Sample Ad...', + }, + { + "type": "vast_url", + "description": "VAST endpoint URL", + "snippet": "https://ads.example.com/vast?campaign=12345&placement=video", + }, + { + "type": "html", + "description": "HTML display ad", + "snippet": '
Ad
', + }, + { + "type": "javascript", + "description": "JavaScript ad tag", + "snippet": '', + }, + { + "type": "iframe", + "description": "iFrame ad tag", + "snippet": '', + }, + { + "type": "daast_url", + "description": "DAAST audio ad URL", + "snippet": "https://audio-ads.example.com/daast?campaign=audio123", + }, + ], + title="Snippet Type", + ), + ] = None + has_performance_data: Annotated[ + Optional[bool], + Field(description="Filter creatives that have performance data when true"), + ] = None + + +class FieldModel(Enum): + created_date = "created_date" + updated_date = "updated_date" + name = "name" + status = "status" + assignment_count = "assignment_count" + performance_score = "performance_score" + + +class Direction(Enum): + asc = "asc" + desc = "desc" + + +class Sort(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + field: Annotated[Optional[FieldModel], Field(description="Field to sort by")] = ( + "created_date" + ) + direction: Annotated[Optional[Direction], Field(description="Sort direction")] = ( + "desc" + ) + + +class Pagination(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + limit: Annotated[ + Optional[int], + Field(description="Maximum number of creatives to return", ge=1, le=100), + ] = 50 + offset: Annotated[ + Optional[int], Field(description="Number of creatives to skip", ge=0) + ] = 0 + + +class Field1(Enum): + creative_id = "creative_id" + name = "name" + format = "format" + status = "status" + created_date = "created_date" + updated_date = "updated_date" + tags = "tags" + assignments = "assignments" + performance = "performance" + sub_assets = "sub_assets" + + +class ListCreativesRequest(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" + filters: Annotated[ + Optional[Filters], Field(description="Filter criteria for querying creatives") + ] = None + sort: Annotated[Optional[Sort], Field(description="Sorting parameters")] = None + pagination: Annotated[ + Optional[Pagination], Field(description="Pagination parameters") + ] = None + include_assignments: Annotated[ + Optional[bool], + Field(description="Include package assignment information in response"), + ] = True + include_performance: Annotated[ + Optional[bool], + Field(description="Include aggregated performance metrics in response"), + ] = False + include_sub_assets: Annotated[ + Optional[bool], + Field( + description="Include sub-assets (for carousel/native formats) in response" + ), + ] = False + fields: Annotated[ + Optional[list[Field1]], + Field( + description="Specific fields to include in response (omit for all fields)" + ), + ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creatives_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creatives_response_json.py new file mode 100644 index 0000000..2cb33e0 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_list_creatives_response_json.py @@ -0,0 +1,341 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_media-buy_list-creatives-response_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 Direction(Enum): + asc = "asc" + desc = "desc" + + +class SortApplied(BaseModel): + field: Optional[str] = None + direction: Optional[Direction] = None + + +class QuerySummary(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + total_matching: Annotated[ + int, + Field( + description="Total number of creatives matching filters (across all pages)", + ge=0, + ), + ] + returned: Annotated[ + int, Field(description="Number of creatives returned in this response", ge=0) + ] + filters_applied: Annotated[ + Optional[list[str]], + Field(description="List of filters that were applied to the query"), + ] = None + sort_applied: Annotated[ + Optional[SortApplied], Field(description="Sort order that was applied") + ] = None + + +class Pagination(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + limit: Annotated[ + int, Field(description="Maximum number of results requested", ge=1) + ] + offset: Annotated[int, Field(description="Number of results skipped", ge=0)] + has_more: Annotated[bool, Field(description="Whether more results are available")] + total_pages: Annotated[ + Optional[int], Field(description="Total number of pages available", ge=0) + ] = None + current_page: Annotated[ + Optional[int], Field(description="Current page number (1-based)", ge=1) + ] = None + + +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 Status13(Enum): + active = "active" + paused = "paused" + ended = "ended" + + +class AssignedPackage(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + package_id: Annotated[str, Field(description="Package identifier")] + package_name: Annotated[ + Optional[str], Field(description="Human-readable package name") + ] = None + assigned_date: Annotated[ + AwareDatetime, Field(description="When this assignment was created") + ] + status: Annotated[Status13, Field(description="Status of this specific assignment")] + + +class Assignments(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + assignment_count: Annotated[ + int, Field(description="Total number of active package assignments", ge=0) + ] + assigned_packages: Annotated[ + Optional[list[AssignedPackage]], + Field(description="List of packages this creative is assigned to"), + ] = None + + +class Performance(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + impressions: Annotated[ + Optional[int], + Field(description="Total impressions across all assignments", ge=0), + ] = None + clicks: Annotated[ + Optional[int], Field(description="Total clicks across all assignments", ge=0) + ] = None + ctr: Annotated[ + Optional[float], + Field(description="Click-through rate (clicks/impressions)", ge=0.0, le=1.0), + ] = None + conversion_rate: Annotated[ + Optional[float], + Field(description="Conversion rate across all assignments", ge=0.0, le=1.0), + ] = None + performance_score: Annotated[ + Optional[float], + Field(description="Aggregated performance score (0-100)", ge=0.0, le=100.0), + ] = None + last_updated: Annotated[ + AwareDatetime, Field(description="When performance data was last updated") + ] + + +class SubAssets(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 SubAssets1(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 Creative(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")] + status: Annotated[ + Status, Field(description="Status of a creative asset", title="Creative Status") + ] + created_date: Annotated[ + AwareDatetime, + Field(description="When the creative was uploaded to the library"), + ] + updated_date: Annotated[ + AwareDatetime, Field(description="When the creative was last modified") + ] + media_url: Annotated[ + Optional[AnyUrl], + Field(description="URL of the creative file (for hosted assets)"), + ] = None + snippet: Annotated[ + Optional[str], + Field( + description="Third-party tag, VAST XML, or code snippet (for third-party assets)" + ), + ] = 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": 'Sample Ad...', + }, + { + "type": "vast_url", + "description": "VAST endpoint URL", + "snippet": "https://ads.example.com/vast?campaign=12345&placement=video", + }, + { + "type": "html", + "description": "HTML display ad", + "snippet": '
Ad
', + }, + { + "type": "javascript", + "description": "JavaScript ad tag", + "snippet": '', + }, + { + "type": "iframe", + "description": "iFrame ad tag", + "snippet": '', + }, + { + "type": "daast_url", + "description": "DAAST audio ad URL", + "snippet": "https://audio-ads.example.com/daast?campaign=audio123", + }, + ], + title="Snippet Type", + ), + ] = 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 + tags: Annotated[ + Optional[list[str]], + Field(description="User-defined tags for organization and searchability"), + ] = None + assignments: Annotated[ + Optional[Assignments], + Field( + description="Current package assignments (included when include_assignments=true)" + ), + ] = None + performance: Annotated[ + Optional[Performance], + Field( + description="Aggregated performance metrics (included when include_performance=true)" + ), + ] = None + sub_assets: Annotated[ + Optional[list[Union[SubAssets, SubAssets1]]], + Field( + description="Sub-assets for multi-asset formats (included when include_sub_assets=true)" + ), + ] = None + + +class StatusSummary(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + approved: Annotated[ + Optional[int], Field(description="Number of approved creatives", ge=0) + ] = None + pending_review: Annotated[ + Optional[int], Field(description="Number of creatives pending review", ge=0) + ] = None + rejected: Annotated[ + Optional[int], Field(description="Number of rejected creatives", ge=0) + ] = None + archived: Annotated[ + Optional[int], Field(description="Number of archived creatives", ge=0) + ] = None + + +class ListCreativesResponse(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + adcp_version: Annotated[ + str, + Field( + description="AdCP schema version used for this response", + pattern="^\\d+\\.\\d+\\.\\d+$", + ), + ] + message: Annotated[str, Field(description="Human-readable result message")] + context_id: Annotated[ + Optional[str], Field(description="Context ID for tracking related operations") + ] = None + query_summary: Annotated[ + QuerySummary, Field(description="Summary of the query that was executed") + ] + pagination: Annotated[ + Pagination, Field(description="Pagination information for navigating results") + ] + creatives: Annotated[ + list[Creative], Field(description="Array of creative assets matching the query") + ] + format_summary: Annotated[ + Optional[dict[str, int]], + Field(description="Breakdown of creatives by format type"), + ] = None + status_summary: Annotated[ + Optional[StatusSummary], Field(description="Breakdown of creatives by status") + ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_package_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_package_request_json.py new file mode 100644 index 0000000..3941e6a --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_package_request_json.py @@ -0,0 +1,156 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_media-buy_package-request_json.json + +from __future__ import annotations + +from enum import Enum +from typing import Annotated, Any, Optional, Union + +from pydantic import BaseModel, ConfigDict, 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 PackageRequest1(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 PackageRequest2(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 PackageRequest(RootModel[Union[PackageRequest1, PackageRequest2]]): + root: Annotated[ + Union[PackageRequest1, PackageRequest2], + Field( + description="Package configuration for media buy creation", + title="Package Request", + ), + ] diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_sync_creatives_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_sync_creatives_request_json.py new file mode 100644 index 0000000..606bdf7 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_sync_creatives_request_json.py @@ -0,0 +1,351 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_media-buy_sync-creatives-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 + + +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 Assets30(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 Assets31(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 Assets32(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 Assets33(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 Assets34(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 Assets35(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 Assets36(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 Assets37(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 Creative(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, + Assets30, + Assets31, + Assets32, + Assets33, + Assets34, + Assets35, + Assets36, + Assets37, + ], + ], + 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 ValidationMode(Enum): + strict = "strict" + lenient = "lenient" + + +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)" + ), + ] + + +class SyncCreativesRequest(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" + creatives: Annotated[ + list[Creative], + Field( + description="Array of creative assets to sync (create or update)", + max_length=100, + ), + ] + patch: Annotated[ + Optional[bool], + Field( + description="When true, only provided fields are updated (partial update). When false, entire creative is replaced (full upsert)." + ), + ] = False + assignments: Annotated[ + Optional[dict[str, list[str]]], + Field(description="Optional bulk assignment of creatives to packages"), + ] = None + delete_missing: Annotated[ + Optional[bool], + Field( + description="When true, creatives not included in this sync will be archived. Use with caution for full library replacement." + ), + ] = False + dry_run: Annotated[ + Optional[bool], + Field( + description="When true, preview changes without applying them. Returns what would be created/updated/deleted." + ), + ] = False + validation_mode: Annotated[ + Optional[ValidationMode], + Field( + description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors." + ), + ] = "strict" + push_notification_config: Annotated[ + Optional[PushNotificationConfig], + Field( + description="Webhook configuration for asynchronous task notifications. Uses A2A-compatible PushNotificationConfig structure. Supports Bearer tokens (simple) or HMAC signatures (production-recommended).", + title="Push Notification Config", + ), + ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_sync_creatives_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_sync_creatives_response_json.py new file mode 100644 index 0000000..1874b07 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_sync_creatives_response_json.py @@ -0,0 +1,125 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_media-buy_sync-creatives-response_json.json + +from __future__ import annotations + +from enum import Enum +from typing import Annotated, Optional + +from pydantic import AnyUrl, 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 Action(Enum): + created = "created" + updated = "updated" + unchanged = "unchanged" + failed = "failed" + deleted = "deleted" + + +class Creative(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + creative_id: Annotated[str, Field(description="Creative ID from the request")] + action: Annotated[Action, Field(description="Action taken for this creative")] + platform_id: Annotated[ + Optional[str], + Field(description="Platform-specific ID assigned to the creative"), + ] = None + changes: Annotated[ + Optional[list[str]], + Field( + description="Field names that were modified (only present when action='updated')" + ), + ] = None + errors: Annotated[ + Optional[list[str]], + Field( + description="Validation or processing errors (only present when action='failed')" + ), + ] = None + warnings: Annotated[ + Optional[list[str]], Field(description="Non-fatal warnings about this creative") + ] = None + preview_url: Annotated[ + Optional[AnyUrl], + Field( + description="Preview URL for generative creatives (only present for generative formats)" + ), + ] = None + expires_at: Annotated[ + Optional[AwareDatetime], + Field( + description="ISO 8601 timestamp when preview link expires (only present when preview_url exists)" + ), + ] = None + assigned_to: Annotated[ + Optional[list[str]], + Field( + description="Package IDs this creative was successfully assigned to (only present when assignments were requested)" + ), + ] = None + assignment_errors: Annotated[ + Optional[dict[str, str]], + Field( + description="Assignment errors by package ID (only present when assignment failures occurred)" + ), + ] = None + + +class SyncCreativesResponse(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + adcp_version: Annotated[ + str, + Field( + description="AdCP schema version used for this response", + pattern="^\\d+\\.\\d+\\.\\d+$", + ), + ] + message: Annotated[ + str, + Field( + description="Human-readable result message (e.g., 'Synced 3 creatives: 2 created, 1 updated')" + ), + ] + context_id: Annotated[ + Optional[str], + Field( + description="Context ID for tracking async operations and conversational approval workflows" + ), + ] = None + 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 + dry_run: Annotated[ + Optional[bool], + Field(description="Whether this was a dry run (no actual changes made)"), + ] = None + creatives: Annotated[ + list[Creative], Field(description="Results for each creative processed") + ] diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_update_media_buy_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_update_media_buy_request_json.py new file mode 100644 index 0000000..2e982f7 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_update_media_buy_request_json.py @@ -0,0 +1,500 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_media-buy_update-media-buy-request_json.json + +from __future__ import annotations + +from enum import Enum +from typing import Annotated, Optional, Union + +from pydantic import AnyUrl, AwareDatetime, 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 Packages(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + package_id: Annotated[str, Field(description="Publisher's ID of package to update")] + buyer_ref: Annotated[ + Optional[str], Field(description="Buyer's reference for the package to update") + ] = None + budget: Annotated[ + Optional[float], + Field( + description="Updated budget allocation for this package in the currency specified by the pricing option", + ge=0.0, + ), + ] = None + active: Annotated[ + Optional[bool], Field(description="Pause/resume specific package") + ] = 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="Update creative assignments") + ] = None + + +class TargetingOverlay6(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 Packages5(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + package_id: Annotated[ + Optional[str], Field(description="Publisher's ID of package to update") + ] = None + buyer_ref: Annotated[ + str, Field(description="Buyer's reference for the package to update") + ] + budget: Annotated[ + Optional[float], + Field( + description="Updated budget allocation for this package in the currency specified by the pricing option", + ge=0.0, + ), + ] = None + active: Annotated[ + Optional[bool], Field(description="Pause/resume specific package") + ] = None + targeting_overlay: Annotated[ + Optional[TargetingOverlay6], + 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="Update creative assignments") + ] = 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 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)" + ), + ] + + +class UpdateMediaBuyRequest1(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_id: Annotated[ + str, Field(description="Publisher's ID of the media buy to update") + ] + buyer_ref: Annotated[ + Optional[str], + Field(description="Buyer's reference for the media buy to update"), + ] = None + active: Annotated[ + Optional[bool], Field(description="Pause/resume the entire media buy") + ] = None + start_time: Annotated[ + Optional[Union[str, AwareDatetime]], + Field( + description="Campaign start timing: 'asap' or ISO 8601 date-time", + title="Start Timing", + ), + ] = None + end_time: Annotated[ + Optional[AwareDatetime], + Field(description="New end date/time in ISO 8601 format"), + ] = None + budget: Annotated[ + Optional[float], + Field( + description="Updated total budget for this media buy. Currency is determined by the pricing_option_id selected in each package.", + ge=0.0, + ), + ] = None + packages: Annotated[ + Optional[list[Union[Packages, Packages5]]], + Field(description="Package-specific updates"), + ] = None + push_notification_config: Annotated[ + Optional[PushNotificationConfig], + Field( + description="Webhook configuration for asynchronous task notifications. Uses A2A-compatible PushNotificationConfig structure. Supports Bearer tokens (simple) or HMAC signatures (production-recommended).", + title="Push Notification Config", + ), + ] = None + + +class TargetingOverlay7(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 Packages6(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + package_id: Annotated[str, Field(description="Publisher's ID of package to update")] + buyer_ref: Annotated[ + Optional[str], Field(description="Buyer's reference for the package to update") + ] = None + budget: Annotated[ + Optional[float], + Field( + description="Updated budget allocation for this package in the currency specified by the pricing option", + ge=0.0, + ), + ] = None + active: Annotated[ + Optional[bool], Field(description="Pause/resume specific package") + ] = None + targeting_overlay: Annotated[ + Optional[TargetingOverlay7], + 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="Update creative assignments") + ] = None + + +class TargetingOverlay8(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 Packages7(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + package_id: Annotated[ + Optional[str], Field(description="Publisher's ID of package to update") + ] = None + buyer_ref: Annotated[ + str, Field(description="Buyer's reference for the package to update") + ] + budget: Annotated[ + Optional[float], + Field( + description="Updated budget allocation for this package in the currency specified by the pricing option", + ge=0.0, + ), + ] = None + active: Annotated[ + Optional[bool], Field(description="Pause/resume specific package") + ] = None + targeting_overlay: Annotated[ + Optional[TargetingOverlay8], + 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="Update creative assignments") + ] = None + + +class Authentication5(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 PushNotificationConfig3(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[ + Authentication5, + Field( + description="Authentication configuration for webhook delivery (A2A-compatible)" + ), + ] + + +class UpdateMediaBuyRequest2(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_id: Annotated[ + Optional[str], Field(description="Publisher's ID of the media buy to update") + ] = None + buyer_ref: Annotated[ + str, Field(description="Buyer's reference for the media buy to update") + ] + active: Annotated[ + Optional[bool], Field(description="Pause/resume the entire media buy") + ] = None + start_time: Annotated[ + Optional[Union[str, AwareDatetime]], + Field( + description="Campaign start timing: 'asap' or ISO 8601 date-time", + title="Start Timing", + ), + ] = None + end_time: Annotated[ + Optional[AwareDatetime], + Field(description="New end date/time in ISO 8601 format"), + ] = None + budget: Annotated[ + Optional[float], + Field( + description="Updated total budget for this media buy. Currency is determined by the pricing_option_id selected in each package.", + ge=0.0, + ), + ] = None + packages: Annotated[ + Optional[list[Union[Packages6, Packages7]]], + Field(description="Package-specific updates"), + ] = None + push_notification_config: Annotated[ + Optional[PushNotificationConfig3], + Field( + description="Webhook configuration for asynchronous task notifications. Uses A2A-compatible PushNotificationConfig structure. Supports Bearer tokens (simple) or HMAC signatures (production-recommended).", + title="Push Notification Config", + ), + ] = None + + +class UpdateMediaBuyRequest( + RootModel[Union[UpdateMediaBuyRequest1, UpdateMediaBuyRequest2]] +): + root: Annotated[ + Union[UpdateMediaBuyRequest1, UpdateMediaBuyRequest2], + Field( + description="Request parameters for updating campaign and package settings", + title="Update Media Buy Request", + ), + ] diff --git a/src/creative_agent/schemas_generated/_schemas_v1_media_buy_update_media_buy_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_update_media_buy_response_json.py new file mode 100644 index 0000000..772c6cb --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_media_buy_update_media_buy_response_json.py @@ -0,0 +1,101 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_media-buy_update-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 AffectedPackage(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + package_id: Annotated[str, Field(description="Publisher's package identifier")] + buyer_ref: Annotated[str, Field(description="Buyer's reference 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 UpdateMediaBuyResponse(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[ + str, Field(description="Publisher's identifier for the media buy") + ] + buyer_ref: Annotated[ + str, Field(description="Buyer's reference identifier for the media buy") + ] + implementation_date: Annotated[ + Optional[AwareDatetime], + Field( + description="ISO 8601 timestamp when changes take effect (null if pending approval)" + ), + ] = None + affected_packages: Annotated[ + Optional[list[AffectedPackage]], + Field(description="Array of packages that were modified"), + ] = None + errors: Annotated[ + Optional[list[Error]], + Field( + description="Task-specific errors and warnings (e.g., partial update failures)" + ), + ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpc_option_json.py b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpc_option_json.py new file mode 100644 index 0000000..8d6fa7b --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpc_option_json.py @@ -0,0 +1,37 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_pricing-options_cpc-option_json.json + +from __future__ import annotations + +from typing import Annotated, Literal, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class CpcPricingOption(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 diff --git a/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpcv_option_json.py b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpcv_option_json.py new file mode 100644 index 0000000..77f6a66 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpcv_option_json.py @@ -0,0 +1,41 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_pricing-options_cpcv-option_json.json + +from __future__ import annotations + +from typing import Annotated, Literal, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class CpcvPricingOption(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 diff --git a/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpm_auction_option_json.py b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpm_auction_option_json.py new file mode 100644 index 0000000..6169faf --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpm_auction_option_json.py @@ -0,0 +1,64 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_pricing-options_cpm-auction-option_json.json + +from __future__ import annotations + +from typing import Annotated, Literal, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +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 CpmAuctionPricingOption(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 diff --git a/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpm_fixed_option_json.py b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpm_fixed_option_json.py new file mode 100644 index 0000000..037f005 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpm_fixed_option_json.py @@ -0,0 +1,41 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_pricing-options_cpm-fixed-option_json.json + +from __future__ import annotations + +from typing import Annotated, Literal, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class CpmFixedRatePricingOption(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 diff --git a/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpp_option_json.py b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpp_option_json.py new file mode 100644 index 0000000..a66350c --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpp_option_json.py @@ -0,0 +1,64 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_pricing-options_cpp-option_json.json + +from __future__ import annotations + +from typing import Annotated, Literal, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class Parameters(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 CppPricingOption(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[ + Parameters, + 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 diff --git a/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpv_option_json.py b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpv_option_json.py new file mode 100644 index 0000000..bb13444 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_cpv_option_json.py @@ -0,0 +1,74 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_pricing-options_cpv-option_json.json + +from __future__ import annotations + +from typing import Annotated, Literal, Optional, Union + +from pydantic import BaseModel, ConfigDict, Field, RootModel + + +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 ViewThreshold11(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, ViewThreshold11] + + +class CpvPricingOption(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 diff --git a/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_flat_rate_option_json.py b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_flat_rate_option_json.py new file mode 100644 index 0000000..7aabb95 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_pricing_options_flat_rate_option_json.py @@ -0,0 +1,101 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_pricing-options_flat-rate-option_json.json + +from __future__ import annotations + +from typing import Annotated, Literal, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class Parameters(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 FlatRatePricingOption(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[Parameters], + 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 diff --git a/src/creative_agent/schemas_generated/_schemas_v1_signals_activate_signal_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_signals_activate_signal_request_json.py new file mode 100644 index 0000000..b93e46b --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_signals_activate_signal_request_json.py @@ -0,0 +1,31 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_signals_activate-signal-request_json.json + +from __future__ import annotations + +from typing import Annotated, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class ActivateSignalRequest(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + adcp_version: Annotated[ + Optional[str], + Field( + description="AdCP schema version for this request", + pattern="^\\d+\\.\\d+\\.\\d+$", + ), + ] = "1.5.0" + signal_agent_segment_id: Annotated[ + str, Field(description="The universal identifier for the signal to activate") + ] + platform: Annotated[str, Field(description="The target platform for activation")] + account: Annotated[ + Optional[str], + Field( + description="Account identifier (required for account-specific activation)" + ), + ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_signals_activate_signal_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_signals_activate_signal_response_json.py new file mode 100644 index 0000000..4a62c26 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_signals_activate_signal_response_json.py @@ -0,0 +1,75 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_signals_activate-signal-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): + pending = "pending" + processing = "processing" + deployed = "deployed" + failed = "failed" + + +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 ActivateSignalResponse(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + adcp_version: Annotated[ + str, + Field( + description="AdCP schema version used for this response", + pattern="^\\d+\\.\\d+\\.\\d+$", + ), + ] + task_id: Annotated[ + str, Field(description="Unique identifier for tracking the activation") + ] + status: Annotated[Status, Field(description="Current status")] + decisioning_platform_segment_id: Annotated[ + Optional[str], + Field(description="The platform-specific ID to use once activated"), + ] = None + estimated_activation_duration_minutes: Annotated[ + Optional[float], + Field(description="Estimated time to complete (optional)", ge=0.0), + ] = None + deployed_at: Annotated[ + Optional[AwareDatetime], + Field(description="Timestamp when activation completed (optional)"), + ] = None + errors: Annotated[ + Optional[list[Error]], + Field( + description="Task-specific errors and warnings (e.g., activation failures, platform issues)" + ), + ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_signals_get_signals_request_json.py b/src/creative_agent/schemas_generated/_schemas_v1_signals_get_signals_request_json.py new file mode 100644 index 0000000..8d3c62e --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_signals_get_signals_request_json.py @@ -0,0 +1,89 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_signals_get-signals-request_json.json + +from __future__ import annotations + +from enum import Enum +from typing import Annotated, Optional, Union + +from pydantic import BaseModel, ConfigDict, Field, RootModel + + +class Account(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + platform: Annotated[str, Field(description="Platform identifier")] + account: Annotated[str, Field(description="Account identifier on that platform")] + + +class Country(RootModel[str]): + root: Annotated[str, Field(pattern="^[A-Z]{2}$")] + + +class DeliverTo(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + platforms: Annotated[ + Union[str, list[str]], + Field(description="Target platforms for signal deployment"), + ] + accounts: Annotated[ + Optional[list[Account]], + Field(description="Specific platform-account combinations"), + ] = None + countries: Annotated[ + list[Country], + Field(description="Countries where signals will be used (ISO codes)"), + ] + + +class CatalogType(Enum): + marketplace = "marketplace" + custom = "custom" + owned = "owned" + + +class Filters(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + catalog_types: Annotated[ + Optional[list[CatalogType]], Field(description="Filter by catalog type") + ] = None + data_providers: Annotated[ + Optional[list[str]], Field(description="Filter by specific data providers") + ] = None + max_cpm: Annotated[ + Optional[float], Field(description="Maximum CPM price filter", ge=0.0) + ] = None + min_coverage_percentage: Annotated[ + Optional[float], + Field(description="Minimum coverage requirement", ge=0.0, le=100.0), + ] = None + + +class GetSignalsRequest(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + adcp_version: Annotated[ + Optional[str], + Field( + description="AdCP schema version for this request", + pattern="^\\d+\\.\\d+\\.\\d+$", + ), + ] = "1.5.0" + signal_spec: Annotated[ + str, Field(description="Natural language description of the desired signals") + ] + deliver_to: Annotated[ + DeliverTo, Field(description="Where the signals need to be delivered") + ] + filters: Annotated[ + Optional[Filters], Field(description="Filters to refine results") + ] = None + max_results: Annotated[ + Optional[int], Field(description="Maximum number of results to return", ge=1) + ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_signals_get_signals_response_json.py b/src/creative_agent/schemas_generated/_schemas_v1_signals_get_signals_response_json.py new file mode 100644 index 0000000..c9ffd4e --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_signals_get_signals_response_json.py @@ -0,0 +1,116 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_signals_get-signals-response_json.json + +from __future__ import annotations + +from enum import Enum +from typing import Annotated, Any, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class SignalType(Enum): + marketplace = "marketplace" + custom = "custom" + owned = "owned" + + +class Scope(Enum): + platform_wide = "platform-wide" + account_specific = "account-specific" + + +class Deployment(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + platform: Annotated[str, Field(description="Platform name")] + account: Annotated[ + Optional[str], Field(description="Specific account if applicable") + ] = None + is_live: Annotated[bool, Field(description="Whether signal is currently active")] + scope: Annotated[Scope, Field(description="Deployment scope")] + decisioning_platform_segment_id: Annotated[ + Optional[str], Field(description="Platform-specific segment ID") + ] = None + estimated_activation_duration_minutes: Annotated[ + Optional[float], Field(description="Time to activate if not live", ge=0.0) + ] = None + + +class Pricing(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + cpm: Annotated[float, Field(description="Cost per thousand impressions", ge=0.0)] + currency: Annotated[str, Field(description="Currency code", pattern="^[A-Z]{3}$")] + + +class Signal(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + signal_agent_segment_id: Annotated[ + str, Field(description="Unique identifier for the signal") + ] + name: Annotated[str, Field(description="Human-readable signal name")] + description: Annotated[str, Field(description="Detailed signal description")] + signal_type: Annotated[SignalType, Field(description="Type of signal")] + data_provider: Annotated[str, Field(description="Name of the data provider")] + coverage_percentage: Annotated[ + float, Field(description="Percentage of audience coverage", ge=0.0, le=100.0) + ] + deployments: Annotated[ + list[Deployment], Field(description="Array of platform deployments") + ] + pricing: Annotated[Pricing, Field(description="Pricing information")] + + +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 GetSignalsResponse(BaseModel): + model_config = ConfigDict( + extra="forbid", + ) + adcp_version: Annotated[ + str, + Field( + description="AdCP schema version used for this response", + pattern="^\\d+\\.\\d+\\.\\d+$", + ), + ] + message: Annotated[ + str, Field(description="Human-readable summary of the signal discovery results") + ] + context_id: Annotated[ + str, Field(description="Session continuity identifier for follow-up requests") + ] + signals: Annotated[list[Signal], Field(description="Array of matching signals")] + errors: Annotated[ + Optional[list[Error]], + Field( + description="Task-specific errors and warnings (e.g., signal discovery or pricing issues)" + ), + ] = None diff --git a/src/creative_agent/schemas_generated/_schemas_v1_standard_formats_asset_types_index_json.py b/src/creative_agent/schemas_generated/_schemas_v1_standard_formats_asset_types_index_json.py new file mode 100644 index 0000000..7da012b --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_standard_formats_asset_types_index_json.py @@ -0,0 +1,18 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_standard-formats_asset-types_index_json.json + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import Field, RootModel + + +class AdcpAssetTypeDefinitionsRegistry(RootModel[Any]): + root: Annotated[ + Any, + Field( + description="Standardized definitions for all asset types used in AdCP creative formats", + title="AdCP Asset Type Definitions Registry", + ), + ] diff --git a/src/creative_agent/schemas_generated/_schemas_v1_standard_formats_index_json.py b/src/creative_agent/schemas_generated/_schemas_v1_standard_formats_index_json.py new file mode 100644 index 0000000..67df991 --- /dev/null +++ b/src/creative_agent/schemas_generated/_schemas_v1_standard_formats_index_json.py @@ -0,0 +1,18 @@ +# generated by datamodel-codegen: +# filename: _schemas_v1_standard-formats_index_json.json + +from __future__ import annotations + +from typing import Annotated, Any + +from pydantic import Field, RootModel + + +class AdcpStandardCreativeFormatsRegistry(RootModel[Any]): + root: Annotated[ + Any, + Field( + description="Authoritative registry of standard creative formats for Ad Context Protocol", + title="AdCP Standard Creative Formats Registry", + ), + ] diff --git a/src/creative_agent/server.py b/src/creative_agent/server.py index a95edf7..8fe20b7 100644 --- a/src/creative_agent/server.py +++ b/src/creative_agent/server.py @@ -9,13 +9,21 @@ from fastmcp import FastMCP from .data.standard_formats import ( + AGENT_CAPABILITIES, + AGENT_NAME, AGENT_URL, filter_formats, get_format_by_id, ) -from .schemas.build import BuildCreativeRequest, BuildCreativeResponse, CreativeOutput -from .schemas.format import ListCreativeFormatsResponse -from .schemas.manifest import PreviewCreativeRequest, PreviewCreativeResponse, PreviewVariant +from .schemas import ( + BuildCreativeRequest, + BuildCreativeResponse, + CreativeOutput, + ListCreativeFormatsResponse, + PreviewCreativeRequest, + PreviewCreativeResponse, + PreviewVariant, +) mcp = FastMCP("adcp-creative-agent") @@ -26,6 +34,11 @@ def list_creative_formats( type: str | None = None, asset_types: list[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, ) -> str: """List all available AdCP creative formats with optional filtering. @@ -34,28 +47,67 @@ def list_creative_formats( format_ids: Return only these specific format IDs type: Filter by format type (audio, video, display, dooh, native, interactive) asset_types: Filter to formats that include these asset types - dimensions: Filter to formats with specific dimensions (e.g., "300x250") + dimensions: (Deprecated) Filter to formats with specific dimensions (e.g., "300x250"). Use min/max filters instead. + max_width: Maximum width in pixels (inclusive). Returns formats with width <= this value. + max_height: Maximum height in pixels (inclusive). Returns formats with height <= this value. + min_width: Minimum width in pixels (inclusive). Returns formats with width >= this value. + min_height: Minimum height in pixels (inclusive). Returns formats with height >= this value. + is_responsive: Filter to responsive formats (adapt to container size) name_search: Search for formats by name (case-insensitive partial match) Returns: JSON string with format list response """ try: + # Cast asset_types to the expected type (filter_formats accepts str or AssetType) formats = filter_formats( format_ids=format_ids, type=type, - asset_types=asset_types, + asset_types=asset_types, # type: ignore[arg-type] # filter_formats accepts list[str] dimensions=dimensions, + max_width=max_width, + max_height=max_height, + min_width=min_width, + min_height=min_height, + is_responsive=is_responsive, name_search=name_search, ) + # Import required types + from .schemas_generated._schemas_v1_media_buy_list_creative_formats_response_json import ( + Capability, + CreativeAgent, + Status, + ) + from .schemas_generated._schemas_v1_media_buy_list_creative_formats_response_json import ( + Format as ResponseFormat, + ) + + # Convert capability strings to Capability enum + capabilities_enum = [Capability(cap) for cap in AGENT_CAPABILITIES] + + # Convert core Format objects to response Format objects + response_formats = [ResponseFormat(**fmt.model_dump(exclude_unset=True)) for fmt in formats] + response = ListCreativeFormatsResponse( - formats=formats, + status=Status.completed, + formats=response_formats, + creative_agents=[ + CreativeAgent( + agent_url=AGENT_URL, + agent_name=AGENT_NAME, + capabilities=capabilities_enum, + ) + ], ) return response.model_dump_json(indent=2) + except ValueError as e: + return json.dumps({"error": f"Invalid input: {e}"}, indent=2) except Exception as e: - return json.dumps({"error": str(e)}) + import traceback + + return json.dumps({"error": f"Server error: {e}", "traceback": traceback.format_exc()[-500:]}, indent=2) @mcp.tool() @@ -84,20 +136,17 @@ def preview_creative( """ try: # Import schema types - from .schemas.manifest import CreativeManifest, PreviewInput - - # Parse manifest dict into CreativeManifest - manifest_obj = CreativeManifest(**creative_manifest) + from .schemas.manifest import PreviewInput # Parse inputs if provided inputs_obj: list[PreviewInput] | None = None if inputs: inputs_obj = [PreviewInput(**inp) for inp in inputs] - # Parse request + # Parse request (creative_manifest stays as dict) request = PreviewCreativeRequest( format_id=format_id, - creative_manifest=manifest_obj, + creative_manifest=creative_manifest, inputs=inputs_obj, template_id=template_id, brand_card=brand_card, @@ -114,10 +163,11 @@ def preview_creative( ) # Validate manifest format_id matches - if request.creative_manifest.format_id != request.format_id: + manifest_format_id = request.creative_manifest.get("format_id") + if manifest_format_id != request.format_id: return json.dumps( { - "error": f"Manifest format_id '{request.creative_manifest.format_id}' does not match request format_id '{request.format_id}'" + "error": f"Manifest format_id '{manifest_format_id}' does not match request format_id '{request.format_id}'" }, indent=2, ) @@ -165,8 +215,15 @@ def preview_creative( ) return response.model_dump_json(indent=2) + except ValueError as e: + return json.dumps({"error": f"Invalid input: {e}"}, indent=2) except Exception as e: - return json.dumps({"error": str(e)}, indent=2) + import traceback + + return json.dumps( + {"error": f"Preview generation failed: {e}", "traceback": traceback.format_exc()[-500:]}, + indent=2, + ) def _generate_preview_variant( @@ -182,17 +239,20 @@ def _generate_preview_variant( # Build hints based on format type hints = PreviewHints() - if format_obj.type == "video": + # Import Type enum for comparisons + from .schemas_generated._schemas_v1_core_format_json import Type + + if format_obj.type == Type.video: hints.primary_media_type = "video" hints.contains_audio = True - if format_obj.requirements and format_obj.requirements.duration_seconds: - hints.estimated_duration_seconds = format_obj.requirements.duration_seconds - elif format_obj.type == "audio": + if format_obj.requirements and format_obj.requirements.get("duration_seconds"): + hints.estimated_duration_seconds = format_obj.requirements["duration_seconds"] + elif format_obj.type == Type.audio: hints.primary_media_type = "audio" hints.contains_audio = True - if format_obj.requirements and format_obj.requirements.duration_seconds: - hints.estimated_duration_seconds = format_obj.requirements.duration_seconds - elif format_obj.type in ["display", "native", "dooh"]: + if format_obj.requirements and format_obj.requirements.get("duration_seconds"): + hints.estimated_duration_seconds = format_obj.requirements["duration_seconds"] + elif format_obj.type in [Type.display, Type.native, Type.dooh]: hints.primary_media_type = "image" hints.contains_audio = False else: @@ -208,7 +268,7 @@ def _generate_preview_variant( embedding = PreviewEmbedding( recommended_sandbox="allow-scripts allow-same-origin", requires_https=False, - supports_fullscreen=format_obj.type in ["video", "interactive"], + supports_fullscreen=format_obj.type in [Type.video, Type.rich_media], ) return PreviewVariant( @@ -252,6 +312,13 @@ def build_creative( JSON string with build response containing creative manifest """ try: + # Validate message length + if not message or len(message) > 10000: + return json.dumps( + {"error": "Message must be between 1 and 10000 characters"}, + indent=2, + ) + # Validate API key is provided if not gemini_api_key: return json.dumps( @@ -311,9 +378,11 @@ def build_creative( indent=2, ) - # For universal/generative formats, get the output format for asset requirements + # For generative formats (identified by having output_format_ids), + # get the output format for asset requirements output_fmt = fmt - if fmt.type == "universal" and fmt.output_format_ids and len(fmt.output_format_ids) > 0: + if fmt.output_format_ids and len(fmt.output_format_ids) > 0: + # Use the first output format ID output_fmt_result = get_format_by_id(fmt.output_format_ids[0]) if not output_fmt_result: return json.dumps( @@ -329,26 +398,53 @@ def build_creative( client = genai.Client(api_key=request.gemini_api_key) # Build prompt for creative generation - # For universal/generative formats, describe what we're generating (the output format) - target_format = output_fmt if fmt.type == "universal" else fmt + # For generative formats, describe what we're generating (the output format) + is_generative = fmt.output_format_ids and len(fmt.output_format_ids) > 0 + target_format = output_fmt if is_generative else fmt format_spec = f"""Format: {fmt.name} -Type: {fmt.type} +Type: {fmt.type.value} Description: {fmt.description} """ - if fmt.type == "universal": + if is_generative: format_spec += f"\nThis will generate a: {output_fmt.name}\n" - if output_fmt.dimensions: - format_spec += f"Dimensions: {output_fmt.dimensions}\n" + if output_fmt.requirements and output_fmt.requirements.get("dimensions"): + format_spec += f"Dimensions: {output_fmt.requirements['dimensions']}\n" format_spec += "\nRequired Assets:\n" - for asset_req in target_format.assets_required: - format_spec += f"- {asset_req.asset_role} ({asset_req.asset_type})" - if asset_req.width and asset_req.height: - format_spec += f" - {asset_req.width}x{asset_req.height}" - format_spec += f": {asset_req.description or 'N/A'}\n" + if target_format.assets_required: + from .schemas_generated._schemas_v1_core_format_json import AssetsRequired1 + + for asset_req in target_format.assets_required: + # Handle both AssetsRequired and AssetsRequired1 (repeatable groups) + if isinstance(asset_req, AssetsRequired1): + # This is a repeatable group (AssetsRequired1) + format_spec += f"- {asset_req.asset_group_id} (repeatable group, {asset_req.min_count}-{asset_req.max_count} items)\n" + for asset in asset_req.assets: + format_spec += f" - {asset.asset_role or asset.asset_id} ({asset.asset_type.value})" + if asset.requirements: + width = asset.requirements.get("width") + height = asset.requirements.get("height") + if width and height: + format_spec += f" - {width}x{height}" + desc = asset.requirements.get("description") + if desc: + format_spec += f": {desc}" + format_spec += "\n" + else: + # This is a regular asset (AssetsRequired) + format_spec += f"- {asset_req.asset_role or asset_req.asset_id} ({asset_req.asset_type.value})" + if asset_req.requirements: + width = asset_req.requirements.get("width") + height = asset_req.requirements.get("height") + if width and height: + format_spec += f" - {width}x{height}" + desc = asset_req.requirements.get("description") + if desc: + format_spec += f": {desc}" + format_spec += "\n" # Add brand context if provided (support both promoted_offerings and deprecated brand_card) brand_data = promoted_offerings or brand_card @@ -378,13 +474,18 @@ def build_creative( if "assets" in brand_data and len(brand_data["assets"]) > 0: brand_context += f"- Available Brand Assets: {len(brand_data['assets'])} assets (logos, images, etc.)\n" + # Import Type and AssetType enums for comparisons + from .schemas_generated._schemas_v1_core_format_json import AssetType, Type + # Check if we need to generate images (check target format, not input generative format) - generate_images = target_format.type == "display" and any( - req.asset_type == "image" for req in target_format.assets_required - ) + generate_images = False + if target_format.assets_required: + generate_images = target_format.type == Type.display and any( + req.asset_type == AssetType.image for req in target_format.assets_required if hasattr(req, "asset_type") + ) # The format_id in the output manifest should be the OUTPUT format - output_format_id = output_fmt.format_id if fmt.type == "universal" else request.format_id + output_format_id = output_fmt.format_id if is_generative else request.format_id if generate_images: prompt = f"""You are a creative generation AI for advertising. @@ -438,8 +539,39 @@ def build_creative( contents: list[Any] = [prompt] # Add brand asset images if provided (from promoted_offerings or brand_card) + import ipaddress + from urllib.parse import urlparse + import httpx + def is_safe_url(url: str) -> bool: + """Validate URL is safe to fetch (no localhost, private IPs, etc.).""" + try: + parsed = urlparse(url) + if parsed.scheme not in ["http", "https"]: + return False + + hostname = parsed.hostname + if not hostname: + return False + + # Block localhost + if hostname.lower() in ["localhost", "127.0.0.1", "::1"]: + return False + + # Block private IP ranges + try: + ip = ipaddress.ip_address(hostname) + if ip.is_private or ip.is_loopback or ip.is_link_local: + return False + except ValueError: + # Hostname is not an IP, that's fine + pass + + return True + except Exception: + return False + # Collect all image assets from various sources image_assets = [] @@ -459,9 +591,12 @@ def build_creative( if isinstance(asset, dict) and asset.get("asset_type") == "image": img_url = asset.get("url") if img_url: + if not is_safe_url(img_url): + print(f"Warning: Blocked potentially unsafe URL: {img_url}") + continue try: # Fetch image from URL - img_response = httpx.get(img_url, timeout=10.0) + img_response = httpx.get(img_url, timeout=10.0, follow_redirects=False) img_response.raise_for_status() img_bytes = img_response.content @@ -547,8 +682,8 @@ def build_creative( else: status = "draft" - # Build output_format_ids list for universal/generative formats - output_format_ids_list = fmt.output_format_ids if fmt.type == "universal" else None + # Build output_format_ids list for generative formats + output_format_ids_list = fmt.output_format_ids if is_generative else None # Build response build_response = BuildCreativeResponse( @@ -575,12 +710,22 @@ def build_creative( except json.JSONDecodeError as e: return json.dumps( - {"error": f"Failed to parse generated creative: {e!s}"}, + { + "error": f"Failed to parse AI-generated creative: {e}", + "context": {"line": e.lineno, "column": e.colno, "format_id": format_id}, + }, indent=2, ) + except ValueError as e: + return json.dumps({"error": f"Invalid input: {e}"}, indent=2) except Exception as e: + import traceback + return json.dumps( - {"error": f"Creative generation failed: {e!s}"}, + { + "error": f"Creative generation failed: {e}", + "context": {"format_id": format_id, "traceback": traceback.format_exc()[-500:]}, + }, indent=2, ) diff --git a/src/creative_agent/storage.py b/src/creative_agent/storage.py index 9660d1f..849d655 100644 --- a/src/creative_agent/storage.py +++ b/src/creative_agent/storage.py @@ -1,5 +1,6 @@ """Tigris storage for preview HTML and assets.""" +import html as html_module import os from typing import Any @@ -56,6 +57,14 @@ def upload_preview_html(preview_id: str, variant_name: str, html_content: str) - return f"https://{BUCKET_NAME}.fly.storage.tigris.dev/{key}" +def sanitize_url(url: str) -> str: + """Sanitize URL for safe HTML insertion.""" + # Block javascript: and data: URIs + if url.lower().startswith(("javascript:", "data:", "vbscript:")): + return "#" + return html_module.escape(url) + + def generate_preview_html(format_obj: Any, manifest: Any, input_set: Any) -> str: """Generate HTML preview content for a creative manifest. @@ -133,18 +142,23 @@ def generate_preview_html(format_obj: Any, manifest: Any, input_set: Any) -> str """ if image_url: - html += f' {format_obj.name}\n' + safe_image_url = sanitize_url(image_url) + safe_format_name = html_module.escape(format_obj.name) + html += f' {safe_format_name}\n' else: - html += f'
{format_obj.name}
\n' + safe_format_name = html_module.escape(format_obj.name) + html += f'
{safe_format_name}
\n' - html += f"""
{input_set.name}
+ safe_input_name = html_module.escape(input_set.name) + html += f"""
{safe_input_name}
" + }, + { + "type": "iframe", + "description": "iFrame ad tag", + "snippet": "" + }, + { + "type": "daast_url", + "description": "DAAST audio ad URL", + "snippet": "https://audio-ads.example.com/daast?campaign=audio123" + } + ], + "usage": { + "vast_xml": { + "format_types": [ + "video" + ], + "security": "XML validation recommended", + "platforms": [ + "Connected TV", + "Video players", + "Mobile apps" + ] + }, + "vast_url": { + "format_types": [ + "video" + ], + "security": "URL validation required", + "platforms": [ + "Connected TV", + "Video players", + "Mobile apps" + ] + }, + "html": { + "format_types": [ + "display" + ], + "security": "HTML sanitization required", + "platforms": [ + "Web display", + "Mobile web" + ] + }, + "javascript": { + "format_types": [ + "display" + ], + "security": "Script validation and sandboxing required", + "platforms": [ + "Web display", + "Programmatic" + ] + }, + "iframe": { + "format_types": [ + "display" + ], + "security": "Secure iframe with restrictions", + "platforms": [ + "Web display", + "Mobile web" + ] + }, + "daast_url": { + "format_types": [ + "audio" + ], + "security": "URL validation required", + "platforms": [ + "Audio streaming", + "Podcast", + "Radio" + ] + } + } +} diff --git a/tests/schemas/v1/_schemas_v1_enums_standard-format-ids_json.json b/tests/schemas/v1/_schemas_v1_enums_standard-format-ids_json.json new file mode 100644 index 0000000..b516bee --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_enums_standard-format-ids_json.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/enums/standard-format-ids.json", + "title": "Standard Format IDs", + "description": "Enumeration of all standard creative format identifiers in AdCP", + "type": "string", + "enum": [ + "display_300x250", + "display_728x90", + "display_320x50", + "display_160x600", + "display_970x250", + "display_336x280", + "display_expandable_300x250", + "display_expandable_728x90", + "display_interstitial_320x480", + "display_interstitial_desktop", + "display_dynamic_300x250", + "display_responsive", + "native_in_feed", + "native_content_recommendation", + "native_product", + "video_skippable_15s", + "video_skippable_30s", + "video_non_skippable_15s", + "video_non_skippable_30s", + "video_outstream_autoplay", + "video_vertical_story", + "video_rewarded_30s", + "video_pause_ad", + "video_ctv_non_skippable_30s", + "audio_standard_15s", + "audio_standard_30s", + "audio_podcast_host_read", + "audio_programmatic", + "universal_carousel", + "universal_canvas", + "universal_takeover", + "universal_gallery", + "universal_reveal", + "dooh_landscape_static", + "dooh_portrait_video" + ], + "categories": { + "display": [ + "display_300x250", + "display_728x90", + "display_320x50", + "display_160x600", + "display_970x250", + "display_336x280", + "display_expandable_300x250", + "display_expandable_728x90", + "display_interstitial_320x480", + "display_interstitial_desktop", + "display_dynamic_300x250", + "display_responsive" + ], + "video": [ + "video_skippable_15s", + "video_skippable_30s", + "video_non_skippable_15s", + "video_non_skippable_30s", + "video_outstream_autoplay", + "video_vertical_story", + "video_rewarded_30s", + "video_pause_ad", + "video_ctv_non_skippable_30s" + ], + "native": [ + "native_in_feed", + "native_content_recommendation", + "native_product" + ], + "audio": [ + "audio_standard_15s", + "audio_standard_30s", + "audio_podcast_host_read", + "audio_programmatic" + ], + "dooh": [ + "dooh_landscape_static", + "dooh_portrait_video" + ], + "universal": [ + "universal_carousel", + "universal_canvas", + "universal_takeover", + "universal_gallery", + "universal_reveal" + ] + }, + "universal_formats": [ + "universal_carousel", + "universal_canvas", + "universal_takeover", + "universal_gallery", + "universal_reveal" + ], + "dynamic_creative_formats": [ + "display_dynamic_300x250" + ] +} diff --git a/tests/schemas/v1/_schemas_v1_enums_task-status_json.json b/tests/schemas/v1/_schemas_v1_enums_task-status_json.json new file mode 100644 index 0000000..e969c46 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_enums_task-status_json.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/enums/task-status.json", + "title": "Task Status", + "description": "Standardized task status values based on A2A TaskState enum. Indicates the current state of any AdCP operation.", + "type": "string", + "enum": [ + "submitted", + "working", + "input-required", + "completed", + "canceled", + "failed", + "rejected", + "auth-required", + "unknown" + ], + "enumDescriptions": { + "submitted": "Task accepted and queued for long-running execution (hours to days). Client should poll with tasks/get or provide webhook_url at protocol level.", + "working": "Agent is actively processing the task, expect completion within 120 seconds", + "input-required": "Task is paused and waiting for input from the user (e.g., clarification, approval)", + "completed": "Task has been successfully completed", + "canceled": "Task was canceled by the user", + "failed": "Task failed due to an error during execution", + "rejected": "Task was rejected by the agent and was not started", + "auth-required": "Task requires authentication to proceed", + "unknown": "Task is in an unknown or indeterminate state" + } +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_add-creative-assets-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_add-creative-assets-request_json.json new file mode 100644 index 0000000..922bf00 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_add-creative-assets-request_json.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/add-creative-assets-request.json", + "title": "Add Creative Assets Request", + "description": "Request parameters for uploading creative assets", + "type": "object", + "properties": { + "media_buy_id": { + "type": "string", + "description": "Publisher's ID of the media buy to add creatives to" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference for the media buy" + }, + "assets": { + "type": "array", + "description": "Array of creative assets to upload", + "items": { + "$ref": "/schemas/v1/core/creative-asset.json" + } + } + }, + "required": [ + "assets" + ], + "oneOf": [ + { + "required": [ + "media_buy_id" + ] + }, + { + "required": [ + "buyer_ref" + ] + } + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_add-creative-assets-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_add-creative-assets-response_json.json new file mode 100644 index 0000000..1359bff --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_add-creative-assets-response_json.json @@ -0,0 +1,92 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/add-creative-assets-response.json", + "title": "Add Creative Assets Response", + "description": "Response payload for add_creative_assets task", + "type": "object", + "properties": { + "asset_statuses": { + "type": "array", + "description": "Array of status information for each uploaded asset", + "items": { + "type": "object", + "properties": { + "creative_id": { + "type": "string", + "description": "The creative ID from the request" + }, + "status": { + "$ref": "/schemas/v1/enums/creative-status.json" + }, + "platform_id": { + "type": "string", + "description": "Platform-specific ID assigned to the creative" + }, + "review_feedback": { + "type": "string", + "description": "Feedback from platform review (if any)" + }, + "suggested_adaptations": { + "type": "array", + "description": "Array of recommended format adaptations", + "items": { + "type": "object", + "properties": { + "adaptation_id": { + "type": "string", + "description": "Unique identifier for this adaptation" + }, + "format_id": { + "type": "string", + "description": "Target format ID for the adaptation" + }, + "name": { + "type": "string", + "description": "Suggested name for the adapted creative" + }, + "description": { + "type": "string", + "description": "What this adaptation does" + }, + "changes_summary": { + "type": "array", + "description": "List of changes that will be made", + "items": { + "type": "string" + } + }, + "rationale": { + "type": "string", + "description": "Why this adaptation is recommended" + }, + "estimated_performance_lift": { + "type": "number", + "description": "Expected performance improvement (percentage)", + "minimum": 0 + } + }, + "required": [ + "adaptation_id", + "format_id", + "name", + "description", + "changes_summary", + "rationale" + ], + "additionalProperties": false + } + } + }, + "required": [ + "creative_id", + "status" + ], + "additionalProperties": false + } + } + }, + "required": [ + "asset_statuses" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_create-media-buy-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_create-media-buy-request_json.json new file mode 100644 index 0000000..cded121 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_create-media-buy-request_json.json @@ -0,0 +1,89 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/create-media-buy-request.json", + "title": "Create Media Buy Request", + "description": "Request parameters for creating a media buy", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.1" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for this media buy" + }, + "packages": { + "type": "array", + "description": "Array of package configurations", + "items": { + "$ref": "/schemas/v1/media-buy/package-request.json" + } + }, + "brand_manifest": { + "$ref": "/schemas/v1/core/brand-manifest-ref.json", + "description": "Brand information manifest serving as the namespace and identity for this media buy. Provides brand context, assets, and product catalog. Can be provided inline or as a URL reference to a hosted manifest. Can be cached and reused across multiple requests." + }, + "promoted_offering": { + "type": "string", + "description": "DEPRECATED: Use brand_manifest instead. Legacy field for describing what is being promoted." + }, + "po_number": { + "type": "string", + "description": "Purchase order number for tracking" + }, + "start_time": { + "$ref": "/schemas/v1/core/start-timing.json" + }, + "end_time": { + "type": "string", + "format": "date-time", + "description": "Campaign end date/time in ISO 8601 format" + }, + "budget": { + "type": "number", + "description": "Total budget for this media buy. Currency is determined by the pricing_option_id selected in each package.", + "minimum": 0 + }, + "reporting_webhook": { + "allOf": [ + { + "$ref": "/schemas/v1/core/push-notification-config.json" + }, + { + "type": "object", + "description": "Optional webhook configuration for automated reporting delivery. Uses push_notification_config structure with additional reporting-specific fields.", + "properties": { + "reporting_frequency": { + "type": "string", + "enum": ["hourly", "daily", "monthly"], + "description": "Frequency for automated reporting delivery. Must be supported by all products in the media buy." + }, + "requested_metrics": { + "type": "array", + "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.", + "items": { + "type": "string", + "enum": ["impressions", "spend", "clicks", "ctr", "video_completions", "completion_rate", "conversions", "viewability", "engagement_rate"] + }, + "uniqueItems": true + } + }, + "required": ["reporting_frequency"] + } + ] + } + }, + "required": ["buyer_ref", "packages", "start_time", "end_time", "budget"], + "oneOf": [ + { + "required": ["promoted_offering"] + }, + { + "required": ["brand_manifest"] + } + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_create-media-buy-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_create-media-buy-response_json.json new file mode 100644 index 0000000..0d5c0c8 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_create-media-buy-response_json.json @@ -0,0 +1,71 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/create-media-buy-response.json", + "title": "Create Media Buy Response", + "description": "Response payload for create_media_buy task", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.1" + }, + "status": { + "$ref": "/schemas/v1/enums/task-status.json", + "description": "Current task state - 'completed' for immediate success, 'working' for operations under 120s, 'submitted' for long-running operations, 'input-required' if approval needed" + }, + "task_id": { + "type": "string", + "description": "Unique identifier for tracking this async operation (present for submitted/working status)" + }, + "media_buy_id": { + "type": "string", + "description": "Publisher's unique identifier for the created media buy" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for this media buy" + }, + "creative_deadline": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp for creative upload deadline" + }, + "packages": { + "type": "array", + "description": "Array of created packages", + "items": { + "type": "object", + "properties": { + "package_id": { + "type": "string", + "description": "Publisher's unique identifier for the package" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for the package" + } + }, + "required": [ + "package_id", + "buyer_ref" + ], + "additionalProperties": false + } + }, + "errors": { + "type": "array", + "description": "Task-specific errors and warnings (e.g., partial package creation failures)", + "items": { + "$ref": "/schemas/v1/core/error.json" + } + } + }, + "required": [ + "adcp_version", + "status", + "buyer_ref" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-request_json.json new file mode 100644 index 0000000..edc1328 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-request_json.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/get-media-buy-delivery-request.json", + "title": "Get Media Buy Delivery Request", + "description": "Request parameters for retrieving comprehensive delivery metrics", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.0" + }, + "media_buy_ids": { + "type": "array", + "description": "Array of publisher media buy IDs to get delivery data for", + "items": { + "type": "string" + } + }, + "buyer_refs": { + "type": "array", + "description": "Array of buyer reference IDs to get delivery data for", + "items": { + "type": "string" + } + }, + "status_filter": { + "oneOf": [ + { + "type": "string", + "enum": [ + "active", + "pending", + "paused", + "completed", + "failed", + "all" + ] + }, + { + "type": "array", + "items": { + "type": "string", + "enum": [ + "active", + "pending", + "paused", + "completed", + "failed" + ] + } + } + ], + "description": "Filter by status. Can be a single status or array of statuses" + }, + "start_date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "description": "Start date for reporting period (YYYY-MM-DD)" + }, + "end_date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "description": "End date for reporting period (YYYY-MM-DD)" + } + }, + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-response_json.json new file mode 100644 index 0000000..dd338a5 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_get-media-buy-delivery-response_json.json @@ -0,0 +1,257 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/get-media-buy-delivery-response.json", + "title": "Get Media Buy Delivery Response", + "description": "Response payload for get_media_buy_delivery task", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "notification_type": { + "type": "string", + "enum": [ + "scheduled", + "final", + "delayed", + "adjusted" + ], + "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" + }, + "partial_data": { + "type": "boolean", + "description": "Indicates if any media buys in this webhook have missing/delayed data (only present in webhook deliveries)" + }, + "unavailable_count": { + "type": "integer", + "minimum": 0, + "description": "Number of media buys with reporting_delayed or failed status (only present in webhook deliveries when partial_data is true)" + }, + "sequence_number": { + "type": "integer", + "minimum": 1, + "description": "Sequential notification number (only present in webhook deliveries, starts at 1)" + }, + "next_expected_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp for next expected notification (only present in webhook deliveries when notification_type is not 'final')" + }, + "reporting_period": { + "type": "object", + "description": "Date range for the report. All periods use UTC timezone.", + "properties": { + "start": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 start timestamp in UTC (e.g., 2024-02-05T00:00:00Z)" + }, + "end": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 end timestamp in UTC (e.g., 2024-02-05T23:59:59Z)" + } + }, + "required": [ + "start", + "end" + ], + "additionalProperties": false + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$" + }, + "aggregated_totals": { + "type": "object", + "description": "Combined metrics across all returned media buys. Only included in API responses (get_media_buy_delivery), not in webhook notifications.", + "properties": { + "impressions": { + "type": "number", + "description": "Total impressions delivered across all media buys", + "minimum": 0 + }, + "spend": { + "type": "number", + "description": "Total amount spent across all media buys", + "minimum": 0 + }, + "clicks": { + "type": "number", + "description": "Total clicks across all media buys (if applicable)", + "minimum": 0 + }, + "video_completions": { + "type": "number", + "description": "Total video completions across all media buys (if applicable)", + "minimum": 0 + }, + "media_buy_count": { + "type": "integer", + "description": "Number of media buys included in the response", + "minimum": 0 + } + }, + "required": [ + "impressions", + "spend", + "media_buy_count" + ], + "additionalProperties": false + }, + "media_buy_deliveries": { + "type": "array", + "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.", + "items": { + "type": "object", + "properties": { + "media_buy_id": { + "type": "string", + "description": "Publisher's media buy identifier" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for this media buy" + }, + "status": { + "type": "string", + "description": "Current media buy status. In webhook context, reporting_delayed indicates data temporarily unavailable.", + "enum": [ + "pending", + "active", + "paused", + "completed", + "failed", + "reporting_delayed" + ] + }, + "message": { + "type": "string", + "description": "Human-readable message (typically present when status is reporting_delayed or failed)" + }, + "expected_availability": { + "type": "string", + "format": "date-time", + "description": "When delayed data is expected to be available (only present when status is reporting_delayed)" + }, + "is_adjusted": { + "type": "boolean", + "description": "Indicates this delivery contains updated data for a previously reported period. Buyer should replace previous period data with these totals." + }, + "pricing_model": { + "$ref": "/schemas/v1/enums/pricing-model.json", + "description": "Pricing model used for this media buy" + }, + "totals": { + "allOf": [ + { + "$ref": "/schemas/v1/core/delivery-metrics.json" + }, + { + "type": "object", + "description": "Aggregate metrics for this media buy across all packages", + "properties": { + "effective_rate": { + "type": "number", + "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')", + "minimum": 0 + } + }, + "required": [ + "spend" + ] + } + ] + }, + "by_package": { + "type": "array", + "description": "Metrics broken down by package", + "items": { + "allOf": [ + { + "$ref": "/schemas/v1/core/delivery-metrics.json" + }, + { + "type": "object", + "properties": { + "package_id": { + "type": "string", + "description": "Publisher's package identifier" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for this package" + }, + "pacing_index": { + "type": "number", + "description": "Delivery pace (1.0 = on track, <1.0 = behind, >1.0 = ahead)", + "minimum": 0 + } + }, + "required": [ + "package_id", + "spend" + ] + } + ] + } + }, + "daily_breakdown": { + "type": "array", + "description": "Day-by-day delivery", + "items": { + "type": "object", + "properties": { + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "description": "Date (YYYY-MM-DD)" + }, + "impressions": { + "type": "number", + "description": "Daily impressions", + "minimum": 0 + }, + "spend": { + "type": "number", + "description": "Daily spend", + "minimum": 0 + } + }, + "required": [ + "date", + "impressions", + "spend" + ], + "additionalProperties": false + } + } + }, + "required": [ + "media_buy_id", + "status", + "totals", + "by_package" + ], + "additionalProperties": false + } + }, + "errors": { + "type": "array", + "description": "Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)", + "items": { + "$ref": "/schemas/v1/core/error.json" + } + } + }, + "required": [ + "adcp_version", + "reporting_period", + "currency", + "media_buy_deliveries" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_get-products-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_get-products-request_json.json new file mode 100644 index 0000000..3598302 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_get-products-request_json.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/get-products-request.json", + "title": "Get Products Request", + "description": "Request parameters for discovering available advertising products", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.0" + }, + "brief": { + "type": "string", + "description": "Natural language description of campaign requirements" + }, + "promoted_offering": { + "type": "string", + "description": "DEPRECATED: Use brand_manifest instead. Legacy field for describing what is being promoted." + }, + "brand_manifest": { + "$ref": "/schemas/v1/core/brand-manifest-ref.json", + "description": "Brand information manifest providing brand context, assets, and product catalog. Can be provided inline or as a URL reference to a hosted manifest." + }, + "filters": { + "type": "object", + "description": "Structured filters for product discovery", + "properties": { + "delivery_type": { + "$ref": "/schemas/v1/enums/delivery-type.json" + }, + "is_fixed_price": { + "type": "boolean", + "description": "Filter for fixed price vs auction products" + }, + "format_types": { + "type": "array", + "description": "Filter by format types", + "items": { + "type": "string", + "enum": ["video", "display", "audio"] + } + }, + "format_ids": { + "type": "array", + "description": "Filter by specific format IDs", + "items": { + "type": "string" + } + }, + "standard_formats_only": { + "type": "boolean", + "description": "Only return products accepting IAB standard formats" + }, + "min_exposures": { + "type": "integer", + "description": "Minimum exposures/impressions needed for measurement validity", + "minimum": 1 + } + }, + "additionalProperties": false + } + }, + "oneOf": [ + { + "required": ["promoted_offering"] + }, + { + "required": ["brand_manifest"] + } + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_get-products-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_get-products-response_json.json new file mode 100644 index 0000000..39805e5 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_get-products-response_json.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/get-products-response.json", + "title": "Get Products Response", + "description": "Response payload for get_products task", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.0" + }, + "status": { + "$ref": "/schemas/v1/enums/task-status.json", + "description": "Current task state", + "default": "completed" + }, + "products": { + "type": "array", + "description": "Array of matching products", + "items": { + "$ref": "/schemas/v1/core/product.json" + } + }, + "errors": { + "type": "array", + "description": "Task-specific errors and warnings (e.g., product filtering issues)", + "items": { + "$ref": "/schemas/v1/core/error.json" + } + } + }, + "required": [ + "adcp_version", + "products" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-request_json.json new file mode 100644 index 0000000..a66987c --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-request_json.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/list-authorized-properties-request.json", + "title": "List Authorized Properties Request", + "description": "Request parameters for discovering all properties this agent is authorized to represent", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.0.0" + }, + "tags": { + "type": "array", + "description": "Filter properties by specific tags (optional)", + "items": { + "type": "string", + "pattern": "^[a-z0-9_]+$", + "description": "Tag to filter by (e.g., 'local_radio', 'premium_content')" + } + } + }, + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-response_json.json new file mode 100644 index 0000000..8fba693 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_list-authorized-properties-response_json.json @@ -0,0 +1,78 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/list-authorized-properties-response.json", + "title": "List Authorized Properties Response", + "description": "Response payload for list_authorized_properties task", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "properties": { + "type": "array", + "description": "Array of all properties this agent is authorized to represent", + "items": { + "$ref": "/schemas/v1/core/property.json" + } + }, + "tags": { + "type": "object", + "description": "Metadata for each tag referenced by properties", + "additionalProperties": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Human-readable name for this tag" + }, + "description": { + "type": "string", + "description": "Description of what this tag represents" + } + }, + "required": [ + "name", + "description" + ], + "additionalProperties": false + } + }, + "primary_channels": { + "type": "array", + "description": "Primary advertising channels represented in this property portfolio. Helps buying agents quickly filter relevance.", + "items": { + "$ref": "/schemas/v1/enums/channels.json" + }, + "minItems": 1 + }, + "primary_countries": { + "type": "array", + "description": "Primary countries (ISO 3166-1 alpha-2 codes) where properties are concentrated. Helps buying agents quickly filter relevance.", + "items": { + "type": "string", + "pattern": "^[A-Z]{2}$" + }, + "minItems": 1 + }, + "portfolio_description": { + "type": "string", + "description": "Markdown-formatted description of the property portfolio, including inventory types, audience characteristics, and special features.", + "minLength": 1, + "maxLength": 5000 + }, + "errors": { + "type": "array", + "description": "Task-specific errors and warnings (e.g., property availability issues)", + "items": { + "$ref": "/schemas/v1/core/error.json" + } + } + }, + "required": [ + "adcp_version", + "properties" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_list-creative-formats-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_list-creative-formats-request_json.json new file mode 100644 index 0000000..1a1d742 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_list-creative-formats-request_json.json @@ -0,0 +1,57 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/list-creative-formats-request.json", + "title": "List Creative Formats Request", + "description": "Request parameters for discovering supported creative formats", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.0" + }, + "format_ids": { + "type": "array", + "description": "Return only these specific format IDs (e.g., from get_products response)", + "items": { + "type": "string" + } + }, + "type": { + "type": "string", + "description": "Filter by format type (technical categories with distinct requirements)", + "enum": [ + "audio", + "video", + "display", + "dooh" + ] + }, + "asset_types": { + "type": "array", + "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.", + "items": { + "type": "string", + "enum": [ + "image", + "video", + "audio", + "text", + "html", + "javascript", + "url" + ] + } + }, + "dimensions": { + "type": "string", + "description": "Filter to formats with specific dimensions (e.g., '300x250', '728x90'). Useful with asset_types to find specific sizes like '300x250 JavaScript'" + }, + "name_search": { + "type": "string", + "description": "Search for formats by name (case-insensitive partial match)" + } + }, + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_list-creative-formats-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_list-creative-formats-response_json.json new file mode 100644 index 0000000..170ba09 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_list-creative-formats-response_json.json @@ -0,0 +1,65 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/list-creative-formats-response.json", + "title": "List Creative Formats Response", + "description": "Response payload for list_creative_formats task", + "type": "object", + "properties": { + "status": { + "$ref": "/schemas/v1/enums/task-status.json", + "description": "Current task state" + }, + "formats": { + "type": "array", + "description": "Full format definitions for all formats this agent supports. Each format's authoritative source is indicated by its agent_url field.", + "items": { + "$ref": "/schemas/v1/core/format.json" + } + }, + "creative_agents": { + "type": "array", + "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.", + "items": { + "type": "object", + "properties": { + "agent_url": { + "type": "string", + "format": "uri", + "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": { + "type": "string", + "description": "Human-readable name for the creative agent" + }, + "capabilities": { + "type": "array", + "description": "Capabilities this creative agent provides", + "items": { + "type": "string", + "enum": [ + "validation", + "assembly", + "generation", + "preview" + ] + } + } + }, + "required": [ + "agent_url" + ] + } + }, + "errors": { + "type": "array", + "description": "Task-specific errors and warnings (e.g., format availability issues)", + "items": { + "$ref": "/schemas/v1/core/error.json" + } + } + }, + "required": [ + "formats" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_list-creatives-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_list-creatives-request_json.json new file mode 100644 index 0000000..d949f60 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_list-creatives-request_json.json @@ -0,0 +1,244 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/list-creatives-request.json", + "title": "List Creatives Request", + "description": "Request parameters for querying creative assets from the centralized library with filtering, sorting, and pagination", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.0" + }, + "filters": { + "type": "object", + "description": "Filter criteria for querying creatives", + "properties": { + "format": { + "type": "string", + "description": "Filter by creative format type (e.g., video, audio, display)" + }, + "formats": { + "type": "array", + "description": "Filter by multiple creative format types", + "items": { + "type": "string" + } + }, + "status": { + "$ref": "/schemas/v1/enums/creative-status.json", + "description": "Filter by creative approval status" + }, + "statuses": { + "type": "array", + "description": "Filter by multiple creative statuses", + "items": { + "$ref": "/schemas/v1/enums/creative-status.json" + } + }, + "tags": { + "type": "array", + "description": "Filter by creative tags (all tags must match)", + "items": { + "type": "string" + } + }, + "tags_any": { + "type": "array", + "description": "Filter by creative tags (any tag must match)", + "items": { + "type": "string" + } + }, + "name_contains": { + "type": "string", + "description": "Filter by creative names containing this text (case-insensitive)" + }, + "creative_ids": { + "type": "array", + "description": "Filter by specific creative IDs", + "items": { + "type": "string" + }, + "maxItems": 100 + }, + "created_after": { + "type": "string", + "format": "date-time", + "description": "Filter creatives created after this date (ISO 8601)" + }, + "created_before": { + "type": "string", + "format": "date-time", + "description": "Filter creatives created before this date (ISO 8601)" + }, + "updated_after": { + "type": "string", + "format": "date-time", + "description": "Filter creatives last updated after this date (ISO 8601)" + }, + "updated_before": { + "type": "string", + "format": "date-time", + "description": "Filter creatives last updated before this date (ISO 8601)" + }, + "assigned_to_package": { + "type": "string", + "description": "Filter creatives assigned to this specific package" + }, + "assigned_to_packages": { + "type": "array", + "description": "Filter creatives assigned to any of these packages", + "items": { + "type": "string" + } + }, + "unassigned": { + "type": "boolean", + "description": "Filter for unassigned creatives when true, assigned creatives when false" + }, + "snippet_type": { + "$ref": "/schemas/v1/enums/snippet-type.json", + "description": "Filter by third-party snippet type" + }, + "has_performance_data": { + "type": "boolean", + "description": "Filter creatives that have performance data when true" + } + }, + "additionalProperties": false + }, + "sort": { + "type": "object", + "description": "Sorting parameters", + "properties": { + "field": { + "type": "string", + "enum": [ + "created_date", + "updated_date", + "name", + "status", + "assignment_count", + "performance_score" + ], + "default": "created_date", + "description": "Field to sort by" + }, + "direction": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "desc", + "description": "Sort direction" + } + }, + "additionalProperties": false + }, + "pagination": { + "type": "object", + "description": "Pagination parameters", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50, + "description": "Maximum number of creatives to return" + }, + "offset": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "Number of creatives to skip" + } + }, + "additionalProperties": false + }, + "include_assignments": { + "type": "boolean", + "default": true, + "description": "Include package assignment information in response" + }, + "include_performance": { + "type": "boolean", + "default": false, + "description": "Include aggregated performance metrics in response" + }, + "include_sub_assets": { + "type": "boolean", + "default": false, + "description": "Include sub-assets (for carousel/native formats) in response" + }, + "fields": { + "type": "array", + "description": "Specific fields to include in response (omit for all fields)", + "items": { + "type": "string", + "enum": [ + "creative_id", + "name", + "format", + "status", + "created_date", + "updated_date", + "tags", + "assignments", + "performance", + "sub_assets" + ] + } + } + }, + "additionalProperties": false, + "examples": [ + { + "description": "List all approved video creatives", + "data": { + "filters": { + "format": "video", + "status": "approved" + } + } + }, + { + "description": "Search for Nike creatives with performance data", + "data": { + "filters": { + "name_contains": "nike", + "has_performance_data": true + }, + "include_performance": true + } + }, + { + "description": "Get unassigned creatives for assignment", + "data": { + "filters": { + "unassigned": true + }, + "sort": { + "field": "created_date", + "direction": "desc" + }, + "pagination": { + "limit": 20 + } + } + }, + { + "description": "Lightweight list with minimal fields", + "data": { + "fields": [ + "creative_id", + "name", + "status" + ], + "include_assignments": false + } + } + ] +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_list-creatives-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_list-creatives-response_json.json new file mode 100644 index 0000000..33551ff --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_list-creatives-response_json.json @@ -0,0 +1,439 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/list-creatives-response.json", + "title": "List Creatives Response", + "description": "Response from creative library query with filtered results, metadata, and optional enriched data", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "message": { + "type": "string", + "description": "Human-readable result message" + }, + "context_id": { + "type": "string", + "description": "Context ID for tracking related operations" + }, + "query_summary": { + "type": "object", + "description": "Summary of the query that was executed", + "properties": { + "total_matching": { + "type": "integer", + "description": "Total number of creatives matching filters (across all pages)", + "minimum": 0 + }, + "returned": { + "type": "integer", + "description": "Number of creatives returned in this response", + "minimum": 0 + }, + "filters_applied": { + "type": "array", + "description": "List of filters that were applied to the query", + "items": { + "type": "string" + } + }, + "sort_applied": { + "type": "object", + "description": "Sort order that was applied", + "properties": { + "field": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + } + } + }, + "required": [ + "total_matching", + "returned" + ], + "additionalProperties": false + }, + "pagination": { + "type": "object", + "description": "Pagination information for navigating results", + "properties": { + "limit": { + "type": "integer", + "description": "Maximum number of results requested", + "minimum": 1 + }, + "offset": { + "type": "integer", + "description": "Number of results skipped", + "minimum": 0 + }, + "has_more": { + "type": "boolean", + "description": "Whether more results are available" + }, + "total_pages": { + "type": "integer", + "description": "Total number of pages available", + "minimum": 0 + }, + "current_page": { + "type": "integer", + "description": "Current page number (1-based)", + "minimum": 1 + } + }, + "required": [ + "limit", + "offset", + "has_more" + ], + "additionalProperties": false + }, + "creatives": { + "type": "array", + "description": "Array of creative assets matching the query", + "items": { + "type": "object", + "properties": { + "creative_id": { + "type": "string", + "description": "Unique identifier for the creative" + }, + "name": { + "type": "string", + "description": "Human-readable creative name" + }, + "format": { + "type": "string", + "description": "Creative format type" + }, + "status": { + "$ref": "/schemas/v1/enums/creative-status.json", + "description": "Current approval status of the creative" + }, + "created_date": { + "type": "string", + "format": "date-time", + "description": "When the creative was uploaded to the library" + }, + "updated_date": { + "type": "string", + "format": "date-time", + "description": "When the creative was last modified" + }, + "media_url": { + "type": "string", + "format": "uri", + "description": "URL of the creative file (for hosted assets)" + }, + "snippet": { + "type": "string", + "description": "Third-party tag, VAST XML, or code snippet (for third-party assets)" + }, + "snippet_type": { + "$ref": "/schemas/v1/enums/snippet-type.json", + "description": "Type of snippet content" + }, + "click_url": { + "type": "string", + "format": "uri", + "description": "Landing page URL for the creative" + }, + "duration": { + "type": "number", + "description": "Duration in milliseconds (for video/audio)", + "minimum": 0 + }, + "width": { + "type": "number", + "description": "Width in pixels (for video/display)", + "minimum": 0 + }, + "height": { + "type": "number", + "description": "Height in pixels (for video/display)", + "minimum": 0 + }, + "tags": { + "type": "array", + "description": "User-defined tags for organization and searchability", + "items": { + "type": "string" + } + }, + "assignments": { + "type": "object", + "description": "Current package assignments (included when include_assignments=true)", + "properties": { + "assignment_count": { + "type": "integer", + "description": "Total number of active package assignments", + "minimum": 0 + }, + "assigned_packages": { + "type": "array", + "description": "List of packages this creative is assigned to", + "items": { + "type": "object", + "properties": { + "package_id": { + "type": "string", + "description": "Package identifier" + }, + "package_name": { + "type": "string", + "description": "Human-readable package name" + }, + "assigned_date": { + "type": "string", + "format": "date-time", + "description": "When this assignment was created" + }, + "status": { + "type": "string", + "enum": [ + "active", + "paused", + "ended" + ], + "description": "Status of this specific assignment" + } + }, + "required": [ + "package_id", + "assigned_date", + "status" + ], + "additionalProperties": false + } + } + }, + "required": [ + "assignment_count" + ], + "additionalProperties": false + }, + "performance": { + "type": "object", + "description": "Aggregated performance metrics (included when include_performance=true)", + "properties": { + "impressions": { + "type": "integer", + "description": "Total impressions across all assignments", + "minimum": 0 + }, + "clicks": { + "type": "integer", + "description": "Total clicks across all assignments", + "minimum": 0 + }, + "ctr": { + "type": "number", + "description": "Click-through rate (clicks/impressions)", + "minimum": 0, + "maximum": 1 + }, + "conversion_rate": { + "type": "number", + "description": "Conversion rate across all assignments", + "minimum": 0, + "maximum": 1 + }, + "performance_score": { + "type": "number", + "description": "Aggregated performance score (0-100)", + "minimum": 0, + "maximum": 100 + }, + "last_updated": { + "type": "string", + "format": "date-time", + "description": "When performance data was last updated" + } + }, + "required": [ + "last_updated" + ], + "additionalProperties": false + }, + "sub_assets": { + "type": "array", + "description": "Sub-assets for multi-asset formats (included when include_sub_assets=true)", + "items": { + "$ref": "/schemas/v1/core/sub-asset.json" + } + } + }, + "required": [ + "creative_id", + "name", + "format", + "status", + "created_date", + "updated_date" + ], + "additionalProperties": false + } + }, + "format_summary": { + "type": "object", + "description": "Breakdown of creatives by format type", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "integer", + "description": "Number of creatives with this format", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "status_summary": { + "type": "object", + "description": "Breakdown of creatives by status", + "properties": { + "approved": { + "type": "integer", + "description": "Number of approved creatives", + "minimum": 0 + }, + "pending_review": { + "type": "integer", + "description": "Number of creatives pending review", + "minimum": 0 + }, + "rejected": { + "type": "integer", + "description": "Number of rejected creatives", + "minimum": 0 + }, + "archived": { + "type": "integer", + "description": "Number of archived creatives", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "required": [ + "adcp_version", + "message", + "query_summary", + "pagination", + "creatives" + ], + "additionalProperties": false, + "examples": [ + { + "description": "Successful library query with results", + "data": { + "adcp_version": "1.5.0", + "message": "Found 3 creatives matching your query", + "context_id": "ctx_list_456789", + "query_summary": { + "total_matching": 3, + "returned": 3, + "filters_applied": [ + "format=video", + "status=approved" + ], + "sort_applied": { + "field": "created_date", + "direction": "desc" + } + }, + "pagination": { + "limit": 50, + "offset": 0, + "has_more": false, + "total_pages": 1, + "current_page": 1 + }, + "creatives": [ + { + "creative_id": "hero_video_30s", + "name": "Brand Hero Video 30s", + "format": "video_30s_vast", + "status": "approved", + "created_date": "2024-01-15T10:30:00Z", + "updated_date": "2024-01-15T14:20:00Z", + "snippet": "https://vast.example.com/video/123", + "snippet_type": "vast_url", + "click_url": "https://example.com/products", + "duration": 30000, + "width": 1920, + "height": 1080, + "tags": [ + "q1_2024", + "video", + "brand_awareness" + ] + } + ], + "format_summary": { + "video_30s_vast": 2, + "display_300x250": 1 + }, + "status_summary": { + "approved": 3, + "pending_review": 0, + "rejected": 0, + "archived": 0 + } + } + }, + { + "description": "Query with assignments and performance data", + "data": { + "adcp_version": "1.5.0", + "message": "Found 1 creative with performance data", + "query_summary": { + "total_matching": 1, + "returned": 1 + }, + "pagination": { + "limit": 50, + "offset": 0, + "has_more": false + }, + "creatives": [ + { + "creative_id": "hero_video_30s", + "name": "Brand Hero Video 30s", + "format": "video_30s_vast", + "status": "approved", + "created_date": "2024-01-15T10:30:00Z", + "updated_date": "2024-01-15T14:20:00Z", + "assignments": { + "assignment_count": 2, + "assigned_packages": [ + { + "package_id": "pkg_ctv_001", + "package_name": "CTV Prime Time", + "assigned_date": "2024-01-16T09:00:00Z", + "status": "active" + } + ] + }, + "performance": { + "impressions": 150000, + "clicks": 1200, + "ctr": 0.008, + "performance_score": 85.2, + "last_updated": "2024-01-20T12:00:00Z" + } + } + ] + } + } + ] +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_package-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_package-request_json.json new file mode 100644 index 0000000..e730c27 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_package-request_json.json @@ -0,0 +1,102 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/package-request.json", + "title": "Package Request", + "description": "Package configuration for media buy creation", + "oneOf": [ + { + "type": "object", + "description": "Package with explicit format IDs", + "properties": { + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for this package" + }, + "products": { + "type": "array", + "description": "Array of product IDs to include in this package", + "items": { + "type": "string" + } + }, + "format_ids": { + "type": "array", + "description": "Array of format IDs that will be used for this package - must be supported by all products", + "items": { + "type": "string", + "description": "Format ID referencing a format from list_creative_formats" + } + }, + "budget": { + "$ref": "/schemas/v1/core/budget.json" + }, + "targeting_overlay": { + "$ref": "/schemas/v1/core/targeting.json" + }, + "creative_ids": { + "type": "array", + "description": "Creative IDs to assign to this package at creation time", + "items": { + "type": "string" + } + } + }, + "required": [ + "buyer_ref", + "products", + "format_ids" + ], + "not": { + "required": [ + "format_selection" + ] + }, + "additionalProperties": false + }, + { + "type": "object", + "description": "Package with dynamic format selection", + "properties": { + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for this package" + }, + "products": { + "type": "array", + "description": "Array of product IDs to include in this package", + "items": { + "type": "string" + } + }, + "format_selection": { + "type": "object", + "description": "Dynamic format selection criteria" + }, + "budget": { + "$ref": "/schemas/v1/core/budget.json" + }, + "targeting_overlay": { + "$ref": "/schemas/v1/core/targeting.json" + }, + "creative_ids": { + "type": "array", + "description": "Creative IDs to assign to this package at creation time", + "items": { + "type": "string" + } + } + }, + "required": [ + "buyer_ref", + "products", + "format_selection" + ], + "not": { + "required": [ + "format_ids" + ] + }, + "additionalProperties": false + } + ] +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_sync-creatives-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_sync-creatives-request_json.json new file mode 100644 index 0000000..5974e44 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_sync-creatives-request_json.json @@ -0,0 +1,138 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/sync-creatives-request.json", + "title": "Sync Creatives Request", + "description": "Request parameters for syncing creative assets with upsert semantics - supports bulk operations, patch updates, and assignment management", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.0" + }, + "creatives": { + "type": "array", + "description": "Array of creative assets to sync (create or update)", + "items": { + "$ref": "/schemas/v1/core/creative-asset.json" + }, + "maxItems": 100 + }, + "patch": { + "type": "boolean", + "default": false, + "description": "When true, only provided fields are updated (partial update). When false, entire creative is replaced (full upsert)." + }, + "assignments": { + "type": "object", + "description": "Optional bulk assignment of creatives to packages", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "array", + "description": "Array of package IDs to assign this creative to", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "delete_missing": { + "type": "boolean", + "default": false, + "description": "When true, creatives not included in this sync will be archived. Use with caution for full library replacement." + }, + "dry_run": { + "type": "boolean", + "default": false, + "description": "When true, preview changes without applying them. Returns what would be created/updated/deleted." + }, + "validation_mode": { + "type": "string", + "enum": [ + "strict", + "lenient" + ], + "default": "strict", + "description": "Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors." + }, + "push_notification_config": { + "$ref": "/schemas/v1/core/push-notification-config.json", + "description": "Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (typically for large bulk operations or manual approval/HITL)." + } + }, + "required": [ + "creatives" + ], + "additionalProperties": false, + "examples": [ + { + "description": "Full sync with hosted video creative", + "data": { + "creatives": [ + { + "creative_id": "hero_video_30s", + "name": "Brand Hero Video 30s", + "format_id": { + "agent_url": "https://creative.adcontextprotocol.org", + "id": "video_standard_30s" + }, + "assets": { + "video": { + "asset_type": "video", + "url": "https://cdn.example.com/hero-video.mp4", + "width": 1920, + "height": 1080, + "duration_ms": 30000 + } + }, + "tags": [ + "q1_2024", + "video" + ] + } + ], + "assignments": { + "hero_video_30s": [ + "pkg_ctv_001", + "pkg_ctv_002" + ] + } + } + }, + { + "description": "Generative creative with approval", + "data": { + "creatives": [ + { + "creative_id": "holiday_hero", + "name": "Holiday Campaign Hero", + "format_id": { + "agent_url": "https://publisher.com/.well-known/adcp/sales", + "id": "premium_bespoke_display" + }, + "assets": { + "promoted_offerings": { + "asset_type": "promoted_offerings", + "url": "https://retailer.com", + "colors": { + "primary": "#C41E3A", + "secondary": "#165B33" + } + }, + "generation_prompt": { + "asset_type": "text", + "content": "Create a warm, festive holiday campaign featuring winter products" + } + }, + "tags": [ + "holiday", + "q4_2024" + ] + } + ] + } + } + ] +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_sync-creatives-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_sync-creatives-response_json.json new file mode 100644 index 0000000..2a01698 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_sync-creatives-response_json.json @@ -0,0 +1,124 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/sync-creatives-response.json", + "title": "Sync Creatives Response", + "description": "Response from creative sync operation with results for each creative", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "message": { + "type": "string", + "description": "Human-readable result message (e.g., 'Synced 3 creatives: 2 created, 1 updated')" + }, + "context_id": { + "type": "string", + "description": "Context ID for tracking async operations and conversational approval workflows" + }, + "status": { + "$ref": "/schemas/v1/enums/task-status.json", + "description": "Current task state - 'completed' for immediate success, 'working' for operations under 120s, 'submitted' for long-running operations", + "default": "completed" + }, + "task_id": { + "type": "string", + "description": "Unique identifier for tracking this async operation (present for submitted/working status)" + }, + "dry_run": { + "type": "boolean", + "description": "Whether this was a dry run (no actual changes made)" + }, + "creatives": { + "type": "array", + "description": "Results for each creative processed", + "items": { + "type": "object", + "properties": { + "creative_id": { + "type": "string", + "description": "Creative ID from the request" + }, + "action": { + "type": "string", + "enum": [ + "created", + "updated", + "unchanged", + "failed", + "deleted" + ], + "description": "Action taken for this creative" + }, + "platform_id": { + "type": "string", + "description": "Platform-specific ID assigned to the creative" + }, + "changes": { + "type": "array", + "description": "Field names that were modified (only present when action='updated')", + "items": { + "type": "string" + } + }, + "errors": { + "type": "array", + "description": "Validation or processing errors (only present when action='failed')", + "items": { + "type": "string" + } + }, + "warnings": { + "type": "array", + "description": "Non-fatal warnings about this creative", + "items": { + "type": "string" + } + }, + "preview_url": { + "type": "string", + "format": "uri", + "description": "Preview URL for generative creatives (only present for generative formats)" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 timestamp when preview link expires (only present when preview_url exists)" + }, + "assigned_to": { + "type": "array", + "description": "Package IDs this creative was successfully assigned to (only present when assignments were requested)", + "items": { + "type": "string" + } + }, + "assignment_errors": { + "type": "object", + "description": "Assignment errors by package ID (only present when assignment failures occurred)", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "string", + "description": "Error message for this package assignment" + } + }, + "additionalProperties": false + } + }, + "required": [ + "creative_id", + "action" + ], + "additionalProperties": false + } + } + }, + "required": [ + "adcp_version", + "message", + "status", + "creatives" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_update-media-buy-request_json.json b/tests/schemas/v1/_schemas_v1_media-buy_update-media-buy-request_json.json new file mode 100644 index 0000000..3cf5193 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_update-media-buy-request_json.json @@ -0,0 +1,106 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/update-media-buy-request.json", + "title": "Update Media Buy Request", + "description": "Request parameters for updating campaign and package settings", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.6.0" + }, + "media_buy_id": { + "type": "string", + "description": "Publisher's ID of the media buy to update" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference for the media buy to update" + }, + "active": { + "type": "boolean", + "description": "Pause/resume the entire media buy" + }, + "start_time": { + "$ref": "/schemas/v1/core/start-timing.json" + }, + "end_time": { + "type": "string", + "format": "date-time", + "description": "New end date/time in ISO 8601 format" + }, + "budget": { + "type": "number", + "description": "Updated total budget for this media buy. Currency is determined by the pricing_option_id selected in each package.", + "minimum": 0 + }, + "packages": { + "type": "array", + "description": "Package-specific updates", + "items": { + "type": "object", + "properties": { + "package_id": { + "type": "string", + "description": "Publisher's ID of package to update" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference for the package to update" + }, + "budget": { + "type": "number", + "description": "Updated budget allocation for this package in the currency specified by the pricing option", + "minimum": 0 + }, + "active": { + "type": "boolean", + "description": "Pause/resume specific package" + }, + "targeting_overlay": { + "$ref": "/schemas/v1/core/targeting.json" + }, + "creative_ids": { + "type": "array", + "description": "Update creative assignments", + "items": { + "type": "string" + } + } + }, + "oneOf": [ + { + "required": [ + "package_id" + ] + }, + { + "required": [ + "buyer_ref" + ] + } + ], + "additionalProperties": false + } + }, + "push_notification_config": { + "$ref": "/schemas/v1/core/push-notification-config.json", + "description": "Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time." + } + }, + "oneOf": [ + { + "required": [ + "media_buy_id" + ] + }, + { + "required": [ + "buyer_ref" + ] + } + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_media-buy_update-media-buy-response_json.json b/tests/schemas/v1/_schemas_v1_media-buy_update-media-buy-response_json.json new file mode 100644 index 0000000..ebd3d13 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_media-buy_update-media-buy-response_json.json @@ -0,0 +1,74 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/media-buy/update-media-buy-response.json", + "title": "Update Media Buy Response", + "description": "Response payload for update_media_buy task", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "status": { + "$ref": "/schemas/v1/enums/task-status.json", + "description": "Current task state - 'completed' for immediate success, 'working' for operations under 120s, 'submitted' for long-running operations, 'input-required' if approval needed" + }, + "task_id": { + "type": "string", + "description": "Unique identifier for tracking this async operation (present for submitted/working status)" + }, + "media_buy_id": { + "type": "string", + "description": "Publisher's identifier for the media buy" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference identifier for the media buy" + }, + "implementation_date": { + "type": [ + "string", + "null" + ], + "format": "date-time", + "description": "ISO 8601 timestamp when changes take effect (null if pending approval)" + }, + "affected_packages": { + "type": "array", + "description": "Array of packages that were modified", + "items": { + "type": "object", + "properties": { + "package_id": { + "type": "string", + "description": "Publisher's package identifier" + }, + "buyer_ref": { + "type": "string", + "description": "Buyer's reference for the package" + } + }, + "required": [ + "package_id", + "buyer_ref" + ], + "additionalProperties": false + } + }, + "errors": { + "type": "array", + "description": "Task-specific errors and warnings (e.g., partial update failures)", + "items": { + "$ref": "/schemas/v1/core/error.json" + } + } + }, + "required": [ + "adcp_version", + "status", + "media_buy_id", + "buyer_ref" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_pricing-options_cpc-option_json.json b/tests/schemas/v1/_schemas_v1_pricing-options_cpc-option_json.json new file mode 100644 index 0000000..0114da7 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_pricing-options_cpc-option_json.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpc-option.json", + "title": "CPC Pricing Option", + "description": "Cost Per Click fixed-rate pricing for performance-driven advertising campaigns", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpc_usd_fixed')" + }, + "pricing_model": { + "type": "string", + "const": "cpc", + "description": "Cost per click" + }, + "rate": { + "type": "number", + "description": "Fixed CPC rate (cost per click)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency"], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_pricing-options_cpcv-option_json.json b/tests/schemas/v1/_schemas_v1_pricing-options_cpcv-option_json.json new file mode 100644 index 0000000..f6d0e5e --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_pricing-options_cpcv-option_json.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpcv-option.json", + "title": "CPCV Pricing Option", + "description": "Cost Per Completed View (100% video/audio completion) fixed-rate pricing", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpcv_usd_guaranteed')" + }, + "pricing_model": { + "type": "string", + "const": "cpcv", + "description": "Cost per completed view (100% completion)" + }, + "rate": { + "type": "number", + "description": "Fixed CPCV rate (cost per 100% completion)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency"], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_pricing-options_cpm-auction-option_json.json b/tests/schemas/v1/_schemas_v1_pricing-options_cpm-auction-option_json.json new file mode 100644 index 0000000..4003e8f --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_pricing-options_cpm-auction-option_json.json @@ -0,0 +1,63 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpm-auction-option.json", + "title": "CPM Auction Pricing Option", + "description": "Cost Per Mille (cost per 1,000 impressions) with auction-based pricing - common for programmatic/non-guaranteed inventory", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpm_usd_auction')" + }, + "pricing_model": { + "type": "string", + "const": "cpm", + "description": "Cost per 1,000 impressions" + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "price_guidance": { + "type": "object", + "description": "Pricing guidance for auction-based CPM bidding", + "properties": { + "floor": { + "type": "number", + "description": "Minimum bid price - publisher will reject bids under this value", + "minimum": 0 + }, + "p25": { + "type": "number", + "description": "25th percentile winning price", + "minimum": 0 + }, + "p50": { + "type": "number", + "description": "Median winning price", + "minimum": 0 + }, + "p75": { + "type": "number", + "description": "75th percentile winning price", + "minimum": 0 + }, + "p90": { + "type": "number", + "description": "90th percentile winning price", + "minimum": 0 + } + }, + "required": ["floor"] + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "price_guidance", "currency"], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_pricing-options_cpm-fixed-option_json.json b/tests/schemas/v1/_schemas_v1_pricing-options_cpm-fixed-option_json.json new file mode 100644 index 0000000..87c33c2 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_pricing-options_cpm-fixed-option_json.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpm-fixed-option.json", + "title": "CPM Fixed Rate Pricing Option", + "description": "Cost Per Mille (cost per 1,000 impressions) with guaranteed fixed rate - common for direct/guaranteed deals", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpm_usd_guaranteed')" + }, + "pricing_model": { + "type": "string", + "const": "cpm", + "description": "Cost per 1,000 impressions" + }, + "rate": { + "type": "number", + "description": "Fixed CPM rate (cost per 1,000 impressions)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency"], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_pricing-options_cpp-option_json.json b/tests/schemas/v1/_schemas_v1_pricing-options_cpp-option_json.json new file mode 100644 index 0000000..5ee9ed0 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_pricing-options_cpp-option_json.json @@ -0,0 +1,54 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpp-option.json", + "title": "CPP Pricing Option", + "description": "Cost Per Point (Gross Rating Point) fixed-rate pricing for TV and audio campaigns requiring demographic measurement", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpp_usd_p18-49')" + }, + "pricing_model": { + "type": "string", + "const": "cpp", + "description": "Cost per Gross Rating Point" + }, + "rate": { + "type": "number", + "description": "Fixed CPP rate (cost per rating point)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "parameters": { + "type": "object", + "description": "CPP-specific parameters for demographic targeting and GRP requirements", + "properties": { + "demographic": { + "type": "string", + "pattern": "^[PMWAC][0-9]{2}(-[0-9]{2}|\\+)$", + "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)" + }, + "min_points": { + "type": "number", + "description": "Minimum GRPs/TRPs required for this pricing option", + "minimum": 0 + } + }, + "required": ["demographic"], + "additionalProperties": false + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency", "parameters"], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_pricing-options_cpv-option_json.json b/tests/schemas/v1/_schemas_v1_pricing-options_cpv-option_json.json new file mode 100644 index 0000000..827678b --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_pricing-options_cpv-option_json.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/cpv-option.json", + "title": "CPV Pricing Option", + "description": "Cost Per View (at publisher-defined threshold) fixed-rate pricing for video/audio", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'cpv_usd_50pct')" + }, + "pricing_model": { + "type": "string", + "const": "cpv", + "description": "Cost per view at threshold" + }, + "rate": { + "type": "number", + "description": "Fixed CPV rate (cost per view)", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "parameters": { + "type": "object", + "description": "CPV-specific parameters defining the view threshold", + "properties": { + "view_threshold": { + "oneOf": [ + { + "type": "number", + "description": "Percentage completion threshold for CPV pricing (0.0 to 1.0, e.g., 0.5 = 50% completion)", + "minimum": 0, + "maximum": 1 + }, + { + "type": "object", + "description": "Time-based view threshold for CPV pricing", + "properties": { + "duration_seconds": { + "type": "integer", + "description": "Seconds of viewing required (e.g., 30 for YouTube-style '30 seconds = view')", + "minimum": 1 + } + }, + "required": ["duration_seconds"], + "additionalProperties": false + } + ] + } + }, + "required": ["view_threshold"], + "additionalProperties": false + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "rate", "currency", "parameters"], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_pricing-options_flat-rate-option_json.json b/tests/schemas/v1/_schemas_v1_pricing-options_flat-rate-option_json.json new file mode 100644 index 0000000..bb14f9f --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_pricing-options_flat-rate-option_json.json @@ -0,0 +1,82 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/pricing-options/flat-rate-option.json", + "title": "Flat Rate Pricing Option", + "description": "Flat rate pricing for DOOH, sponsorships, and time-based campaigns - fixed cost regardless of delivery volume", + "type": "object", + "properties": { + "pricing_option_id": { + "type": "string", + "description": "Unique identifier for this pricing option within the product (e.g., 'flat_rate_usd_24h_takeover')" + }, + "pricing_model": { + "type": "string", + "const": "flat_rate", + "description": "Fixed cost regardless of delivery volume" + }, + "rate": { + "type": "number", + "description": "Flat rate cost", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "ISO 4217 currency code", + "pattern": "^[A-Z]{3}$", + "examples": ["USD", "EUR", "GBP", "JPY"] + }, + "is_fixed": { + "type": "boolean", + "description": "Whether this is a fixed rate (true) or auction-based (false)", + "const": true + }, + "parameters": { + "type": "object", + "description": "Flat rate parameters for DOOH and time-based campaigns", + "properties": { + "duration_hours": { + "type": "number", + "description": "Duration in hours for time-based flat rate pricing (DOOH)", + "minimum": 0 + }, + "sov_percentage": { + "type": "number", + "description": "Guaranteed share of voice as percentage (DOOH, 0-100)", + "minimum": 0, + "maximum": 100 + }, + "loop_duration_seconds": { + "type": "integer", + "description": "Duration of ad loop rotation in seconds (DOOH)", + "minimum": 1 + }, + "min_plays_per_hour": { + "type": "integer", + "description": "Minimum number of times ad plays per hour (DOOH frequency guarantee)", + "minimum": 0 + }, + "venue_package": { + "type": "string", + "description": "Named venue package identifier for DOOH (e.g., 'times_square_network', 'airport_terminals')" + }, + "estimated_impressions": { + "type": "integer", + "description": "Estimated impressions for this flat rate option (informational, commonly used with SOV or time-based DOOH)", + "minimum": 0 + }, + "daypart": { + "type": "string", + "description": "Specific daypart for time-based pricing (e.g., 'morning_commute', 'evening_prime', 'overnight')" + } + }, + "additionalProperties": false + }, + "min_spend_per_package": { + "type": "number", + "description": "Minimum spend requirement per package using this pricing option, in the specified currency", + "minimum": 0 + } + }, + "required": ["pricing_option_id", "pricing_model", "currency", "is_fixed", "rate"], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_signals_activate-signal-request_json.json b/tests/schemas/v1/_schemas_v1_signals_activate-signal-request_json.json new file mode 100644 index 0000000..94ee048 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_signals_activate-signal-request_json.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/signals/activate-signal-request.json", + "title": "Activate Signal Request", + "description": "Request parameters for activating a signal on a specific platform/account", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.5.0" + }, + "signal_agent_segment_id": { + "type": "string", + "description": "The universal identifier for the signal to activate" + }, + "platform": { + "type": "string", + "description": "The target platform for activation" + }, + "account": { + "type": "string", + "description": "Account identifier (required for account-specific activation)" + } + }, + "required": [ + "signal_agent_segment_id", + "platform" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_signals_activate-signal-response_json.json b/tests/schemas/v1/_schemas_v1_signals_activate-signal-response_json.json new file mode 100644 index 0000000..8220868 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_signals_activate-signal-response_json.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/signals/activate-signal-response.json", + "title": "Activate Signal Response", + "description": "Response payload for activate_signal task", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "task_id": { + "type": "string", + "description": "Unique identifier for tracking the activation" + }, + "status": { + "type": "string", + "description": "Current status", + "enum": [ + "pending", + "processing", + "deployed", + "failed" + ] + }, + "decisioning_platform_segment_id": { + "type": "string", + "description": "The platform-specific ID to use once activated" + }, + "estimated_activation_duration_minutes": { + "type": "number", + "description": "Estimated time to complete (optional)", + "minimum": 0 + }, + "deployed_at": { + "type": "string", + "format": "date-time", + "description": "Timestamp when activation completed (optional)" + }, + "errors": { + "type": "array", + "description": "Task-specific errors and warnings (e.g., activation failures, platform issues)", + "items": { + "$ref": "/schemas/v1/core/error.json" + } + } + }, + "required": [ + "adcp_version", + "task_id", + "status" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_signals_get-signals-request_json.json b/tests/schemas/v1/_schemas_v1_signals_get-signals-request_json.json new file mode 100644 index 0000000..aae6b9f --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_signals_get-signals-request_json.json @@ -0,0 +1,122 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/signals/get-signals-request.json", + "title": "Get Signals Request", + "description": "Request parameters for discovering signals based on description", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version for this request", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "default": "1.5.0" + }, + "signal_spec": { + "type": "string", + "description": "Natural language description of the desired signals" + }, + "deliver_to": { + "type": "object", + "description": "Where the signals need to be delivered", + "properties": { + "platforms": { + "oneOf": [ + { + "type": "string", + "const": "all" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ], + "description": "Target platforms for signal deployment" + }, + "accounts": { + "type": "array", + "description": "Specific platform-account combinations", + "items": { + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Platform identifier" + }, + "account": { + "type": "string", + "description": "Account identifier on that platform" + } + }, + "required": [ + "platform", + "account" + ], + "additionalProperties": false + } + }, + "countries": { + "type": "array", + "description": "Countries where signals will be used (ISO codes)", + "items": { + "type": "string", + "pattern": "^[A-Z]{2}$" + } + } + }, + "required": [ + "platforms", + "countries" + ], + "additionalProperties": false + }, + "filters": { + "type": "object", + "description": "Filters to refine results", + "properties": { + "catalog_types": { + "type": "array", + "description": "Filter by catalog type", + "items": { + "type": "string", + "enum": [ + "marketplace", + "custom", + "owned" + ] + } + }, + "data_providers": { + "type": "array", + "description": "Filter by specific data providers", + "items": { + "type": "string" + } + }, + "max_cpm": { + "type": "number", + "description": "Maximum CPM price filter", + "minimum": 0 + }, + "min_coverage_percentage": { + "type": "number", + "description": "Minimum coverage requirement", + "minimum": 0, + "maximum": 100 + } + }, + "additionalProperties": false + }, + "max_results": { + "type": "integer", + "description": "Maximum number of results to return", + "minimum": 1 + } + }, + "required": [ + "signal_spec", + "deliver_to" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_signals_get-signals-response_json.json b/tests/schemas/v1/_schemas_v1_signals_get-signals-response_json.json new file mode 100644 index 0000000..1e2ba1f --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_signals_get-signals-response_json.json @@ -0,0 +1,155 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/signals/get-signals-response.json", + "title": "Get Signals Response", + "description": "Response payload for get_signals task", + "type": "object", + "properties": { + "adcp_version": { + "type": "string", + "description": "AdCP schema version used for this response", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "message": { + "type": "string", + "description": "Human-readable summary of the signal discovery results" + }, + "context_id": { + "type": "string", + "description": "Session continuity identifier for follow-up requests" + }, + "signals": { + "type": "array", + "description": "Array of matching signals", + "items": { + "type": "object", + "properties": { + "signal_agent_segment_id": { + "type": "string", + "description": "Unique identifier for the signal" + }, + "name": { + "type": "string", + "description": "Human-readable signal name" + }, + "description": { + "type": "string", + "description": "Detailed signal description" + }, + "signal_type": { + "type": "string", + "description": "Type of signal", + "enum": [ + "marketplace", + "custom", + "owned" + ] + }, + "data_provider": { + "type": "string", + "description": "Name of the data provider" + }, + "coverage_percentage": { + "type": "number", + "description": "Percentage of audience coverage", + "minimum": 0, + "maximum": 100 + }, + "deployments": { + "type": "array", + "description": "Array of platform deployments", + "items": { + "type": "object", + "properties": { + "platform": { + "type": "string", + "description": "Platform name" + }, + "account": { + "type": [ + "string", + "null" + ], + "description": "Specific account if applicable" + }, + "is_live": { + "type": "boolean", + "description": "Whether signal is currently active" + }, + "scope": { + "type": "string", + "description": "Deployment scope", + "enum": [ + "platform-wide", + "account-specific" + ] + }, + "decisioning_platform_segment_id": { + "type": "string", + "description": "Platform-specific segment ID" + }, + "estimated_activation_duration_minutes": { + "type": "number", + "description": "Time to activate if not live", + "minimum": 0 + } + }, + "required": [ + "platform", + "is_live", + "scope" + ], + "additionalProperties": false + } + }, + "pricing": { + "type": "object", + "description": "Pricing information", + "properties": { + "cpm": { + "type": "number", + "description": "Cost per thousand impressions", + "minimum": 0 + }, + "currency": { + "type": "string", + "description": "Currency code", + "pattern": "^[A-Z]{3}$" + } + }, + "required": [ + "cpm", + "currency" + ], + "additionalProperties": false + } + }, + "required": [ + "signal_agent_segment_id", + "name", + "description", + "signal_type", + "data_provider", + "coverage_percentage", + "deployments", + "pricing" + ], + "additionalProperties": false + } + }, + "errors": { + "type": "array", + "description": "Task-specific errors and warnings (e.g., signal discovery or pricing issues)", + "items": { + "$ref": "/schemas/v1/core/error.json" + } + } + }, + "required": [ + "adcp_version", + "message", + "context_id", + "signals" + ], + "additionalProperties": false +} diff --git a/tests/schemas/v1/_schemas_v1_standard-formats_asset-types_index_json.json b/tests/schemas/v1/_schemas_v1_standard-formats_asset-types_index_json.json new file mode 100644 index 0000000..50db731 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_standard-formats_asset-types_index_json.json @@ -0,0 +1,72 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/standard-formats/asset-types/index.json", + "title": "AdCP Asset Type Definitions Registry", + "description": "Standardized definitions for all asset types used in AdCP creative formats", + "version": "2.0.0", + "lastUpdated": "2025-09-03", + "asset_types": { + "video": { + "description": "Video file asset", + "schema": "/schemas/v1/standard-formats/asset-types/video.json" + }, + "image": { + "description": "Static image asset", + "schema": "/schemas/v1/standard-formats/asset-types/image.json" + }, + "text": { + "description": "Text content asset", + "schema": "/schemas/v1/standard-formats/asset-types/text.json" + }, + "url": { + "description": "URL/link asset", + "schema": "/schemas/v1/standard-formats/asset-types/url.json" + }, + "audio": { + "description": "Audio file asset", + "schema": "/schemas/v1/standard-formats/asset-types/audio.json" + }, + "html": { + "description": "HTML5 creative asset", + "schema": "/schemas/v1/standard-formats/asset-types/html.json" + }, + "dynamic_endpoint": { + "description": "Dynamic creative endpoint (DCO webhook)", + "schema": "/schemas/v1/standard-formats/asset-types/dynamic_endpoint.json" + }, + "javascript": { + "description": "JavaScript-based dynamic creative", + "schema": "/schemas/v1/standard-formats/asset-types/javascript.json" + }, + "template": { + "description": "Template-based dynamic creative with data binding", + "schema": "/schemas/v1/standard-formats/asset-types/template.json" + } + }, + "common_properties": { + "asset_id": { + "description": "Unique identifier for this asset within the format, used by orchestrators to map uploaded assets", + "type": "string", + "pattern": "^[a-z0-9_]+$" + }, + "asset_type": { + "description": "The type of asset", + "type": "string", + "enum": [ + "video", + "image", + "text", + "url", + "audio", + "html", + "dynamic_endpoint", + "javascript", + "template" + ] + }, + "required": { + "description": "Whether this asset is mandatory for the creative format", + "type": "boolean" + } + } +} diff --git a/tests/schemas/v1/_schemas_v1_standard-formats_index_json.json b/tests/schemas/v1/_schemas_v1_standard-formats_index_json.json new file mode 100644 index 0000000..eaeaa91 --- /dev/null +++ b/tests/schemas/v1/_schemas_v1_standard-formats_index_json.json @@ -0,0 +1,260 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/standard-formats/index.json", + "title": "AdCP Standard Creative Formats Registry", + "description": "Authoritative registry of standard creative formats for Ad Context Protocol", + "version": "1.0.0", + "adcp_compatibility": [ + "1.0.0", + "1.1.0" + ], + "lastUpdated": "2025-09-02", + "categories": { + "display": { + "description": "Standard display banner formats", + "formats": { + "display_300x250": { + "schema": "/schemas/v1/standard-formats/display/display_300x250.json", + "name": "Medium Rectangle Banner", + "dimensions": "300x250", + "is_foundational": false + }, + "display_728x90": { + "schema": "/schemas/v1/standard-formats/display/display_728x90.json", + "name": "Leaderboard Banner", + "dimensions": "728x90", + "is_foundational": false + }, + "display_320x50": { + "schema": "/schemas/v1/standard-formats/display/display_320x50.json", + "name": "Mobile Banner", + "dimensions": "320x50", + "is_foundational": false + }, + "display_native": { + "schema": "/schemas/v1/standard-formats/display/display_native.json", + "name": "Native Ad", + "dimensions": "responsive", + "is_foundational": false + }, + "display_160x600": { + "schema": "/schemas/v1/standard-formats/display/display_160x600.json", + "name": "Wide Skyscraper", + "dimensions": "160x600", + "is_foundational": false + }, + "display_970x250": { + "schema": "/schemas/v1/standard-formats/display/display_970x250.json", + "name": "Billboard", + "dimensions": "970x250", + "is_foundational": false + }, + "display_336x280": { + "schema": "/schemas/v1/standard-formats/display/display_336x280.json", + "name": "Large Rectangle", + "dimensions": "336x280", + "is_foundational": false + }, + "display_interstitial_320x480": { + "schema": "/schemas/v1/standard-formats/display/display_interstitial_320x480.json", + "name": "Mobile Interstitial", + "dimensions": "320x480", + "is_foundational": false + }, + "display_dynamic_300x250": { + "schema": "/schemas/v1/standard-formats/display/display_dynamic_300x250.json", + "name": "Dynamic Creative Banner 300x250", + "dimensions": "300x250", + "is_dynamic": true, + "is_foundational": false + } + } + }, + "video": { + "description": "Video creative formats", + "formats": { + "video_15s_hosted": { + "schema": "/schemas/v1/standard-formats/video/video_15s_hosted.json", + "name": "15-second Hosted Video", + "duration": "15s", + "is_foundational": false + }, + "video_30s_hosted": { + "schema": "/schemas/v1/standard-formats/video/video_30s_hosted.json", + "name": "30-second Hosted Video", + "duration": "30s", + "is_foundational": false + }, + "video_15s_vast": { + "schema": "/schemas/v1/standard-formats/video/video_15s_vast.json", + "name": "15-second Video via VAST", + "duration": "15s", + "is_foundational": false + }, + "video_30s_vast": { + "schema": "/schemas/v1/standard-formats/video/video_30s_vast.json", + "name": "30-second Video via VAST", + "duration": "30s", + "is_foundational": false + }, + "video_vertical_15s_hosted": { + "schema": "/schemas/v1/standard-formats/video/video_vertical_15s_hosted.json", + "name": "15-second Vertical Video", + "duration": "15s", + "aspect_ratio": "9:16", + "is_foundational": false + }, + "video_ctv_30s_hosted": { + "schema": "/schemas/v1/standard-formats/video/video_ctv_30s_hosted.json", + "name": "30-second Connected TV Video", + "duration": "30s", + "is_foundational": false + }, + "video_outstream_native": { + "schema": "/schemas/v1/standard-formats/video/video_outstream_native.json", + "name": "Outstream Native Video", + "duration": "6-30s", + "placement": "in-feed", + "is_foundational": false + }, + "video_story_vertical": { + "schema": "/schemas/v1/standard-formats/video/video_story_vertical.json", + "name": "Vertical Story Video", + "duration": "5-15s", + "aspect_ratio": "9:16", + "is_foundational": false + } + } + }, + "audio": { + "description": "Audio creative formats", + "formats": { + "audio_standard": { + "schema": "/schemas/v1/standard-formats/audio/audio_standard.json", + "name": "Standard Audio Ad", + "duration": "variable", + "is_foundational": false + }, + "audio_podcast": { + "schema": "/schemas/v1/standard-formats/audio/audio_podcast.json", + "name": "Podcast Audio Ad", + "duration": "variable", + "is_foundational": false + } + } + }, + "rich_media": { + "description": "Interactive and rich media formats", + "formats": { + "rich_interstitial": { + "schema": "/schemas/v1/standard-formats/rich_media/rich_interstitial.json", + "name": "Full-screen Interactive Ad", + "is_foundational": false + } + } + }, + "dooh": { + "description": "Digital out-of-home formats", + "formats": { + "dooh_standard": { + "schema": "/schemas/v1/standard-formats/dooh/dooh_standard.json", + "name": "Digital Out-of-Home Display", + "is_foundational": false + } + } + }, + "native": { + "description": "Native advertising formats", + "formats": { + "native_responsive": { + "schema": "/schemas/v1/standard-formats/native/native_responsive.json", + "name": "Responsive Native Ad", + "is_foundational": false, + "openrtb_native": "1.2" + } + } + }, + "retail": { + "description": "Retail media and e-commerce formats", + "formats": { + "retail_product_carousel": { + "schema": "/schemas/v1/standard-formats/retail/retail_product_carousel.json", + "name": "Product Carousel", + "is_foundational": false, + "products": "3-10" + } + } + }, + "foundational": { + "description": "Publisher-agnostic foundational formats that work across multiple publishers", + "formats": { + "foundational_immersive_canvas": { + "schema": "/schemas/v1/standard-formats/foundational/foundational_immersive_canvas.json", + "name": "Premium Immersive Canvas", + "publisher_coverage": "8+ major publishers", + "is_foundational": true + }, + "foundational_product_carousel": { + "schema": "/schemas/v1/standard-formats/foundational/foundational_product_carousel.json", + "name": "Multi-product Interactive Carousel", + "publisher_coverage": "7+ major publishers", + "is_foundational": true + }, + "foundational_expandable": { + "schema": "/schemas/v1/standard-formats/foundational/foundational_expandable.json", + "name": "Banner with Expandable Canvas", + "publisher_coverage": "10+ major publishers", + "is_foundational": true + }, + "foundational_scroll_reveal": { + "schema": "/schemas/v1/standard-formats/foundational/foundational_scroll_reveal.json", + "name": "Scroll-triggered Mobile Experience", + "publisher_coverage": "6+ major publishers", + "is_foundational": true + }, + "foundational_video_15s": { + "schema": "/schemas/v1/standard-formats/video/foundational_video_15s.json", + "name": "Universal 15-second Video", + "duration": "15s", + "publisher_coverage": "All publishers", + "is_foundational": true + }, + "foundational_video_30s": { + "schema": "/schemas/v1/standard-formats/video/foundational_video_30s.json", + "name": "Universal 30-second Video", + "duration": "30s", + "publisher_coverage": "All publishers", + "is_foundational": true + } + } + } + }, + "asset_types": { + "schema": "/schemas/v1/standard-formats/asset-types/index.json", + "description": "Standardized asset type definitions used across all formats" + }, + "total_formats": 28, + "foundational_formats": 6, + "format_extension_guide": { + "description": "Publishers can extend standard formats using the 'extends' field", + "example_extension": { + "format_id": "publisher_premium_canvas", + "extends": "foundational_immersive_canvas", + "publisher": "example_publisher", + "modifications": { + "additional_placements": [ + "hero", + "mid_article" + ], + "custom_requirements": { + "brand_safety_disclaimer": "required" + } + } + } + }, + "versioning": { + "current_version": "1.0.0", + "changelog_url": "/schemas/v1/standard-formats/CHANGELOG.md", + "migration_guide_url": "/schemas/v1/standard-formats/MIGRATION.md" + } +} diff --git a/tests/schemas/v1/index.json b/tests/schemas/v1/index.json new file mode 100644 index 0000000..9fbfc95 --- /dev/null +++ b/tests/schemas/v1/index.json @@ -0,0 +1,317 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "/schemas/v1/index.json", + "title": "AdCP Schema Registry v1", + "version": "1.0.0", + "description": "Registry of all AdCP JSON schemas for validation and discovery", + "adcp_version": "1.6.1", + "standard_formats_version": "1.0.0", + "versioning": { + "note": "All request/response schemas include adcp_version field. Compatibility follows semantic versioning rules." + }, + "lastUpdated": "2025-10-04", + "baseUrl": "/schemas/v1", + "schemas": { + "core": { + "description": "Core data models used throughout AdCP", + "schemas": { + "product": { + "$ref": "/schemas/v1/core/product.json", + "description": "Represents available advertising inventory" + }, + "media-buy": { + "$ref": "/schemas/v1/core/media-buy.json", + "description": "Represents a purchased advertising campaign" + }, + "package": { + "$ref": "/schemas/v1/core/package.json", + "description": "A specific product within a media buy (line item)" + }, + "creative-asset": { + "$ref": "/schemas/v1/core/creative-asset.json", + "description": "Creative asset for upload to library - supports both hosted assets and third-party snippets" + }, + "targeting": { + "$ref": "/schemas/v1/core/targeting.json", + "description": "Audience targeting criteria" + }, + "budget": { + "$ref": "/schemas/v1/core/budget.json", + "description": "Budget configuration for a media buy or package" + }, + "frequency-cap": { + "$ref": "/schemas/v1/core/frequency-cap.json", + "description": "Frequency capping settings" + }, + "format": { + "$ref": "/schemas/v1/core/format.json", + "description": "Represents a creative format with its requirements" + }, + "measurement": { + "$ref": "/schemas/v1/core/measurement.json", + "description": "Measurement capabilities included with a product" + }, + "creative-policy": { + "$ref": "/schemas/v1/core/creative-policy.json", + "description": "Creative requirements and restrictions for a product" + }, + "response": { + "$ref": "/schemas/v1/core/response.json", + "description": "Standard response structure (MCP)" + }, + "error": { + "$ref": "/schemas/v1/core/error.json", + "description": "Standard error structure" + }, + "sub-asset": { + "$ref": "/schemas/v1/core/sub-asset.json", + "description": "Sub-asset for multi-asset creative formats" + }, + "creative-assignment": { + "$ref": "/schemas/v1/core/creative-assignment.json", + "description": "Assignment of a creative asset to a package" + }, + "creative-library-item": { + "$ref": "/schemas/v1/core/creative-library-item.json", + "description": "Creative asset as it appears in the centralized library" + }, + "performance-feedback": { + "$ref": "/schemas/v1/core/performance-feedback.json", + "description": "Performance feedback data for a media buy or package" + }, + "property": { + "$ref": "/schemas/v1/core/property.json", + "description": "An advertising property that can be validated via adagents.json" + } + } + }, + "enums": { + "description": "Enumerated types and constants", + "schemas": { + "delivery-type": { + "$ref": "/schemas/v1/enums/delivery-type.json", + "description": "Type of inventory delivery" + }, + "media-buy-status": { + "$ref": "/schemas/v1/enums/media-buy-status.json", + "description": "Status of a media buy" + }, + "package-status": { + "$ref": "/schemas/v1/enums/package-status.json", + "description": "Status of a package" + }, + "creative-status": { + "$ref": "/schemas/v1/enums/creative-status.json", + "description": "Status of a creative asset" + }, + "pacing": { + "$ref": "/schemas/v1/enums/pacing.json", + "description": "Budget pacing strategy" + }, + "frequency-cap-scope": { + "$ref": "/schemas/v1/enums/frequency-cap-scope.json", + "description": "Scope for frequency cap application" + }, + "standard-format-ids": { + "$ref": "/schemas/v1/enums/standard-format-ids.json", + "description": "Enumeration of all standard creative format identifiers" + }, + "snippet-type": { + "$ref": "/schemas/v1/enums/snippet-type.json", + "description": "Types of third-party creative snippets (VAST, HTML, JavaScript, etc.)" + }, + "identifier-types": { + "$ref": "/schemas/v1/enums/identifier-types.json", + "description": "Valid identifier types for property identification across different media types" + }, + "task-status": { + "$ref": "/schemas/v1/enums/task-status.json", + "description": "Standardized task status values based on A2A TaskState enum" + } + } + }, + "media-buy": { + "description": "Media buy task request/response schemas", + "supporting-schemas": { + "package-request": { + "$ref": "/schemas/v1/media-buy/package-request.json", + "description": "Package configuration for media buy creation - used within create_media_buy request" + } + }, + "tasks": { + "get-products": { + "request": { + "$ref": "/schemas/v1/media-buy/get-products-request.json", + "description": "Request parameters for discovering available advertising products" + }, + "response": { + "$ref": "/schemas/v1/media-buy/get-products-response.json", + "description": "Response payload for get_products task" + } + }, + "list-creative-formats": { + "request": { + "$ref": "/schemas/v1/media-buy/list-creative-formats-request.json", + "description": "Request parameters for discovering supported creative formats" + }, + "response": { + "$ref": "/schemas/v1/media-buy/list-creative-formats-response.json", + "description": "Response payload for list_creative_formats task" + } + }, + "create-media-buy": { + "request": { + "$ref": "/schemas/v1/media-buy/create-media-buy-request.json", + "description": "Request parameters for creating a media buy" + }, + "response": { + "$ref": "/schemas/v1/media-buy/create-media-buy-response.json", + "description": "Response payload for create_media_buy task" + } + }, + "sync-creatives": { + "request": { + "$ref": "/schemas/v1/media-buy/sync-creatives-request.json", + "description": "Request parameters for syncing creative assets with upsert semantics" + }, + "response": { + "$ref": "/schemas/v1/media-buy/sync-creatives-response.json", + "description": "Response payload for sync_creatives task" + } + }, + "list-creatives": { + "request": { + "$ref": "/schemas/v1/media-buy/list-creatives-request.json", + "description": "Request parameters for querying creative library with filtering and pagination" + }, + "response": { + "$ref": "/schemas/v1/media-buy/list-creatives-response.json", + "description": "Response payload for list_creatives task" + } + }, + "update-media-buy": { + "request": { + "$ref": "/schemas/v1/media-buy/update-media-buy-request.json", + "description": "Request parameters for updating campaign and package settings" + }, + "response": { + "$ref": "/schemas/v1/media-buy/update-media-buy-response.json", + "description": "Response payload for update_media_buy task" + } + }, + "get-media-buy-delivery": { + "request": { + "$ref": "/schemas/v1/media-buy/get-media-buy-delivery-request.json", + "description": "Request parameters for retrieving comprehensive delivery metrics" + }, + "response": { + "$ref": "/schemas/v1/media-buy/get-media-buy-delivery-response.json", + "description": "Response payload for get_media_buy_delivery task" + } + }, + "list-authorized-properties": { + "request": { + "$ref": "/schemas/v1/media-buy/list-authorized-properties-request.json", + "description": "Request parameters for discovering all properties this agent is authorized to represent" + }, + "response": { + "$ref": "/schemas/v1/media-buy/list-authorized-properties-response.json", + "description": "Response payload for list_authorized_properties task" + } + }, + "provide-performance-feedback": { + "request": { + "$ref": "/schemas/v1/media-buy/provide-performance-feedback-request.json", + "description": "Request parameters for sharing performance outcomes with publishers" + }, + "response": { + "$ref": "/schemas/v1/media-buy/provide-performance-feedback-response.json", + "description": "Response payload for provide_performance_feedback task" + } + }, + "build-creative": { + "request": { + "$ref": "/schemas/v1/media-buy/build-creative-request.json", + "description": "Request parameters for AI-powered creative generation" + }, + "response": { + "$ref": "/schemas/v1/media-buy/build-creative-response.json", + "description": "Response payload for build_creative task" + } + }, + "manage-creative-library": { + "request": { + "$ref": "/schemas/v1/media-buy/manage-creative-library-request.json", + "description": "Request parameters for managing creative library assets" + }, + "response": { + "$ref": "/schemas/v1/media-buy/manage-creative-library-response.json", + "description": "Response payload for manage_creative_library task" + } + } + } + }, + "signals": { + "description": "Signals protocol task request/response schemas", + "tasks": { + "get-signals": { + "request": { + "$ref": "/schemas/v1/signals/get-signals-request.json", + "description": "Request parameters for discovering signals based on description" + }, + "response": { + "$ref": "/schemas/v1/signals/get-signals-response.json", + "description": "Response payload for get_signals task" + } + }, + "activate-signal": { + "request": { + "$ref": "/schemas/v1/signals/activate-signal-request.json", + "description": "Request parameters for activating a signal on a specific platform/account" + }, + "response": { + "$ref": "/schemas/v1/signals/activate-signal-response.json", + "description": "Response payload for activate_signal task" + } + } + } + }, + "adagents": { + "description": "Authorized sales agents file format specification", + "$ref": "/schemas/v1/adagents.json", + "file_location": "/.well-known/adagents.json", + "purpose": "Declares which sales agents are authorized to sell a publisher's advertising inventory" + }, + "standard-formats": { + "description": "Standard creative formats registry and schemas", + "$ref": "/schemas/v1/standard-formats/index.json", + "asset_types": { + "$ref": "/schemas/v1/standard-formats/asset-types/index.json", + "description": "Standardized asset type definitions" + } + } + }, + "usage": { + "validation": "Use these schemas to validate AdCP requests and responses", + "codeGeneration": "Generate client SDKs using these schemas", + "documentation": "Reference schemas for API documentation", + "testing": "Validate test fixtures and examples" + }, + "examples": [ + { + "language": "javascript", + "description": "JavaScript validation example", + "code": "const Ajv = require('ajv'); const ajv = new Ajv(); const schema = require('./schemas/v1/core/product.json'); const validate = ajv.compile(schema);" + }, + { + "language": "python", + "description": "Python validation example", + "code": "import jsonschema; schema = {...}; jsonschema.validate(data, schema)" + }, + { + "language": "java", + "description": "Java validation example", + "code": "// Use everit-org/json-schema or similar library" + } + ] +} diff --git a/tests/smoke/test_server_startup.py b/tests/smoke/test_server_startup.py index 1c440d3..bb5740f 100644 --- a/tests/smoke/test_server_startup.py +++ b/tests/smoke/test_server_startup.py @@ -54,9 +54,9 @@ def test_preview_creative_logic(): # Verify display format exists fmt = get_format_by_id("display_300x250_image") assert fmt is not None - assert fmt.type == "display" + assert fmt.type.value == "display" # Verify video format exists fmt = get_format_by_id("video_standard_15s") assert fmt is not None - assert fmt.type == "video" + assert fmt.type.value == "video" diff --git a/uv.lock b/uv.lock index 718a3ba..11171be 100644 --- a/uv.lock +++ b/uv.lock @@ -24,6 +24,8 @@ dependencies = [ [package.dev-dependencies] dev = [ { name = "boto3-stubs", extra = ["s3"] }, + { name = "datamodel-code-generator" }, + { name = "jsonref" }, { name = "mypy" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -48,6 +50,8 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "boto3-stubs", extras = ["s3"], specifier = ">=1.35.0" }, + { name = "datamodel-code-generator", specifier = ">=0.26.0" }, + { name = "jsonref", specifier = ">=1.1.0" }, { name = "mypy", specifier = ">=1.18.2" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-asyncio", specifier = ">=1.1.0" }, @@ -80,6 +84,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, ] +[[package]] +name = "argcomplete" +version = "3.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, +] + [[package]] name = "attrs" version = "25.4.0" @@ -101,6 +114,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" }, ] +[[package]] +name = "black" +version = "25.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, + { name = "pytokens" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/43/20b5c90612d7bdb2bdbcceeb53d588acca3bb8f0e4c5d5c751a2c8fdd55a/black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619", size = 648393, upload-time = "2025-09-19T00:27:37.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/8e/319cfe6c82f7e2d5bfb4d3353c6cc85b523d677ff59edc61fdb9ee275234/black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0", size = 1742012, upload-time = "2025-09-19T00:33:08.678Z" }, + { url = "https://files.pythonhosted.org/packages/94/cc/f562fe5d0a40cd2a4e6ae3f685e4c36e365b1f7e494af99c26ff7f28117f/black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4", size = 1581421, upload-time = "2025-09-19T00:35:25.937Z" }, + { url = "https://files.pythonhosted.org/packages/84/67/6db6dff1ebc8965fd7661498aea0da5d7301074b85bba8606a28f47ede4d/black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e", size = 1655619, upload-time = "2025-09-19T00:30:49.241Z" }, + { url = "https://files.pythonhosted.org/packages/10/10/3faef9aa2a730306cf469d76f7f155a8cc1f66e74781298df0ba31f8b4c8/black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a", size = 1342481, upload-time = "2025-09-19T00:31:29.625Z" }, + { url = "https://files.pythonhosted.org/packages/48/99/3acfea65f5e79f45472c45f87ec13037b506522719cd9d4ac86484ff51ac/black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175", size = 1742165, upload-time = "2025-09-19T00:34:10.402Z" }, + { url = "https://files.pythonhosted.org/packages/3a/18/799285282c8236a79f25d590f0222dbd6850e14b060dfaa3e720241fd772/black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f", size = 1581259, upload-time = "2025-09-19T00:32:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ce/883ec4b6303acdeca93ee06b7622f1fa383c6b3765294824165d49b1a86b/black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831", size = 1655583, upload-time = "2025-09-19T00:30:44.505Z" }, + { url = "https://files.pythonhosted.org/packages/21/17/5c253aa80a0639ccc427a5c7144534b661505ae2b5a10b77ebe13fa25334/black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357", size = 1343428, upload-time = "2025-09-19T00:32:13.839Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/863c90dcd3f9d41b109b7f19032ae0db021f0b2a81482ba0a1e28c84de86/black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae", size = 203363, upload-time = "2025-09-19T00:27:35.724Z" }, +] + [[package]] name = "boto3" version = "1.40.50" @@ -442,6 +480,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/8b/2c95f0645c6f40211896375e6fa51f504b8ccb29c21f6ae661fe87ab044e/cyclopts-3.24.0-py3-none-any.whl", hash = "sha256:809d04cde9108617106091140c3964ee6fceb33cecdd537f7ffa360bde13ed71", size = 86154, upload-time = "2025-09-08T15:40:56.41Z" }, ] +[[package]] +name = "datamodel-code-generator" +version = "0.35.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argcomplete" }, + { name = "black" }, + { name = "genson" }, + { name = "inflect" }, + { name = "isort" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/e1/dbf7c2edb1b1db1f4fd472ee92f985ec97d58902512013d9c4584108329c/datamodel_code_generator-0.35.0.tar.gz", hash = "sha256:46805fa2515d3871f6bfafce9aa63128e735a7a6a4cfcbf9c27b3794ee4ea846", size = 459915, upload-time = "2025-10-09T19:26:49.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/ef/0ed17459fe6076219fcd45f69a0bb4bd1cb041b39095ca2946808a9b5f04/datamodel_code_generator-0.35.0-py3-none-any.whl", hash = "sha256:c356d1e4a555f86667a4262db03d4598a30caeda8f51786555fd269c8abb806b", size = 121436, upload-time = "2025-10-09T19:26:48.437Z" }, +] + [[package]] name = "dnspython" version = "2.8.0" @@ -530,6 +588,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/c7/562ff39f25de27caec01e4c1e88cbb5fcae5160802ba3d90be33165df24f/fastmcp-2.12.4-py3-none-any.whl", hash = "sha256:56188fbbc1a9df58c537063f25958c57b5c4d715f73e395c41b51550b247d140", size = 329090, upload-time = "2025-09-26T16:43:25.314Z" }, ] +[[package]] +name = "genson" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/cf/2303c8ad276dcf5ee2ad6cf69c4338fd86ef0f471a5207b069adf7a393cf/genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37", size = 34919, upload-time = "2024-05-15T22:08:49.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/5c/e226de133afd8bb267ec27eead9ae3d784b95b39a287ed404caab39a5f50/genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7", size = 21470, upload-time = "2024-05-15T22:08:47.056Z" }, +] + [[package]] name = "google-auth" version = "2.41.1" @@ -618,6 +685,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "inflect" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/c6/943357d44a21fd995723d07ccaddd78023eace03c1846049a2645d4324a3/inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f", size = 73751, upload-time = "2024-12-28T17:11:18.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -636,6 +716,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, ] +[[package]] +name = "isort" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/82/fa43935523efdfcce6abbae9da7f372b627b27142c3419fcf13bf5b0c397/isort-6.1.0.tar.gz", hash = "sha256:9b8f96a14cfee0677e78e941ff62f03769a06d412aabb9e2a90487b3b7e8d481", size = 824325, upload-time = "2025-10-01T16:26:45.027Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/cc/9b681a170efab4868a032631dea1e8446d8ec718a7f657b94d49d1a12643/isort-6.1.0-py3-none-any.whl", hash = "sha256:58d8927ecce74e5087aef019f778d4081a3b6c98f15a80ba35782ca8a2097784", size = 94329, upload-time = "2025-10-01T16:26:43.291Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + [[package]] name = "jmespath" version = "1.0.1" @@ -645,6 +746,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, ] +[[package]] +name = "jsonref" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, +] + [[package]] name = "jsonschema" version = "4.25.1" @@ -1047,6 +1157,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, ] +[[package]] +name = "platformdirs" +version = "4.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, +] + [[package]] name = "pluggy" version = "1.6.0" @@ -1286,6 +1405,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, ] +[[package]] +name = "pytokens" +version = "0.1.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/5f/e959a442435e24f6fb5a01aec6c657079ceaca1b3baf18561c3728d681da/pytokens-0.1.10.tar.gz", hash = "sha256:c9a4bfa0be1d26aebce03e6884ba454e842f186a59ea43a6d3b25af58223c044", size = 12171, upload-time = "2025-02-19T14:51:22.001Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e5/63bed382f6a7a5ba70e7e132b8b7b8abbcf4888ffa6be4877698dcfbed7d/pytokens-0.1.10-py3-none-any.whl", hash = "sha256:db7b72284e480e69fb085d9f251f66b3d2df8b7166059261258ff35f50fb711b", size = 12046, upload-time = "2025-02-19T14:51:18.694Z" }, +] + [[package]] name = "pywin32" version = "311" @@ -1598,6 +1726,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] +[[package]] +name = "typeguard" +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/68/71c1a15b5f65f40e91b65da23b8224dad41349894535a97f63a52e462196/typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74", size = 75203, upload-time = "2025-06-18T09:56:07.624Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e", size = 34874, upload-time = "2025-06-18T09:56:05.999Z" }, +] + [[package]] name = "types-awscrt" version = "0.28.1"