Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 57 additions & 89 deletions schemas/v1/_schemas_v1_core_webhook-payload_json.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "/schemas/v1/core/webhook-payload.json",
"title": "Webhook Payload",
"description": "Payload structure sent to webhook endpoints when async task status changes. Combines protocol-level task metadata with domain-specific response data. This schema represents what your webhook handler will receive when a task transitions from 'submitted' to a terminal or intermediate state.",
"description": "Payload structure sent to webhook endpoints when async task status changes. Protocol-level fields are at the top level and the task-specific payload is nested under the 'result' field. This schema represents what your webhook handler will receive when a task transitions from 'submitted' to a terminal or intermediate state.",
"type": "object",
"properties": {
"operation_id": {
"type": "string",
"description": "Publisher-defined operation identifier correlating a sequence of task updates across webhooks."
},
"task_id": {
"type": "string",
"description": "Unique identifier for this task. Use this to correlate webhook notifications with the original task submission."
Expand All @@ -25,20 +29,10 @@
"$ref": "/schemas/v1/enums/task-status.json",
"description": "Current task status. Webhooks are only triggered for status changes after initial submission (e.g., submitted \u2192 input-required, submitted \u2192 completed, submitted \u2192 failed)."
},
"created_at": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp when the task was initially created. Useful for tracking operation duration."
},
"updated_at": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp when the task status was last updated. This matches the timestamp when the webhook was triggered."
},
"completed_at": {
"timestamp": {
"type": "string",
"format": "date-time",
"description": "ISO 8601 timestamp when the task reached a terminal state (completed, failed, or canceled). Only present for terminal states."
"description": "ISO 8601 timestamp when this webhook was generated."
},
"message": {
"type": "string",
Expand Down Expand Up @@ -75,128 +69,102 @@
},
"additionalProperties": false
},
"error": {
"type": "object",
"description": "Error details for failed tasks. Only present when status is 'failed'.",
"properties": {
"code": {
"type": "string",
"description": "Error code for programmatic handling"
"result": {
"type": [
"object"
],
"description": "Task-specific payload for this status update. For 'completed', contains the final result. For 'input-required', may contain approval or clarification context. Optional for non-terminal updates.",
"oneOf": [
{
"$ref": "/schemas/v1/media-buy/create-media-buy-response.json"
},
"message": {
"type": "string",
"description": "Detailed error message"
{
"$ref": "/schemas/v1/media-buy/update-media-buy-response.json"
},
"details": {
"type": "object",
"description": "Additional error context",
"properties": {
"domain": {
"type": "string",
"description": "AdCP domain where error occurred",
"enum": [
"media-buy",
"signals"
]
},
"operation": {
"type": "string",
"description": "Specific operation that failed"
},
"specific_context": {
"type": "object",
"description": "Domain-specific error context",
"additionalProperties": true
}
},
"additionalProperties": true
{
"$ref": "/schemas/v1/media-buy/sync-creatives-response.json"
},
{
"$ref": "/schemas/v1/signals/activate-signal-response.json"
},
{
"$ref": "/schemas/v1/signals/get-signals-response.json"
}
},
"required": [
"code",
"message"
]
},
"error": {
"type": [
"string",
"null"
],
"additionalProperties": false
"description": "Error message for failed tasks. Only present when status is 'failed'."
}
},
"required": [
"task_id",
"task_type",
"domain",
"status",
"created_at",
"updated_at"
"timestamp"
],
"additionalProperties": true,
"notes": [
"Webhooks are ONLY triggered when the initial response status is 'submitted' (long-running operations)",
"Webhook payloads include protocol-level fields (task_id, task_type, domain, status, timestamps) PLUS the full task-specific response data",
"The task-specific response data is merged at the top level of the webhook payload (not nested in a 'payload' field)",
"For example, a create_media_buy webhook will include task_id, task_type, domain, status, AND media_buy_id, packages, creative_deadline, etc.",
"Webhook payloads include protocol-level fields (operation_id, task_type, status, optional task_id/context_id/timestamp/message) and the task-specific payload nested under 'result'",
"The task-specific response data is NOT merged at the top level; it is contained entirely within the 'result' field",
"For example, a create_media_buy webhook will include operation_id, task_type, status, and result.buyer_ref, result.media_buy_id, result.packages, etc.",
"Your webhook handler receives the complete information needed to process the result without making additional API calls"
],
"examples": [
{
"description": "Webhook for input-required status (human approval needed)",
"data": {
"operation_id": "op_456",
"task_id": "task_456",
"task_type": "create_media_buy",
"domain": "media-buy",
"status": "input-required",
"created_at": "2025-01-22T10:00:00Z",
"updated_at": "2025-01-22T10:15:00Z",
"timestamp": "2025-01-22T10:15:00Z",
"context_id": "ctx_abc123",
"message": "Campaign budget $150K requires VP approval to proceed",
"buyer_ref": "nike_q1_campaign_2024"
"result": {
"buyer_ref": "nike_q1_campaign_2024"
}
}
},
{
"description": "Webhook for completed create_media_buy",
"data": {
"operation_id": "op_456",
"task_id": "task_456",
"task_type": "create_media_buy",
"domain": "media-buy",
"status": "completed",
"created_at": "2025-01-22T10:00:00Z",
"updated_at": "2025-01-22T10:30:00Z",
"completed_at": "2025-01-22T10:30:00Z",
"timestamp": "2025-01-22T10:30:00Z",
"message": "Media buy created successfully with 2 packages ready for creative assignment",
"media_buy_id": "mb_12345",
"buyer_ref": "nike_q1_campaign_2024",
"creative_deadline": "2024-01-30T23:59:59Z",
"packages": [
{
"package_id": "pkg_12345_001",
"buyer_ref": "nike_ctv_package"
}
]
"result": {
"media_buy_id": "mb_12345",
"buyer_ref": "nike_q1_campaign_2024",
"creative_deadline": "2024-01-30T23:59:59Z",
"packages": [
{
"package_id": "pkg_12345_001",
"buyer_ref": "nike_ctv_package"
}
]
}
}
},
{
"description": "Webhook for failed sync_creatives",
"data": {
"operation_id": "op_789",
"task_id": "task_789",
"task_type": "sync_creatives",
"domain": "media-buy",
"status": "failed",
"created_at": "2025-01-22T10:45:00Z",
"updated_at": "2025-01-22T10:46:00Z",
"completed_at": "2025-01-22T10:46:00Z",
"timestamp": "2025-01-22T10:46:00Z",
"message": "Creative sync failed due to invalid asset URLs",
"error": {
"code": "invalid_assets",
"message": "One or more creative assets could not be accessed",
"details": {
"domain": "media-buy",
"operation": "sync_creatives",
"specific_context": {
"failed_creatives": [
"creative_001",
"creative_003"
]
}
}
}
"error": "invalid_assets: One or more creative assets could not be accessed"
}
}
]
Expand Down
8 changes: 4 additions & 4 deletions schemas/v1/_schemas_v1_core_webhook-payload_json.json.meta
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"etag": "W/\"6901f913-1ce0\"",
"last-modified": "Wed, 29 Oct 2025 11:22:59 GMT",
"downloaded_at": "2025-10-29T12:14:52.976640",
"etag": "W/\"69086ad9-191b\"",
"last-modified": "Mon, 03 Nov 2025 08:42:01 GMT",
"downloaded_at": "2025-11-03T09:02:20.976217",
"schema_ref": "/schemas/v1/core/webhook-payload.json",
"content_hash": "3037a3a80eb9e20dc2034a6701bf871e944f910e3ea1fec0885361ed06ace8eb"
"content_hash": "f95f24fd723beb6ed5c6f76fc77e1e28516b358dc111570a0d7d30e11cc58115"
}
6 changes: 3 additions & 3 deletions schemas/v1/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"title": "AdCP Schema Registry v1",
"version": "1.0.0",
"description": "Registry of all AdCP JSON schemas for validation and discovery",
"adcp_version": "2.3.0",
"adcp_version": "2.4.0",
"standard_formats_version": "2.0.0",
"versioning": {
"note": "AdCP uses path-based versioning. The schema URL path (/schemas/v1/) indicates the version. Individual request/response schemas do NOT include adcp_version fields. Compatibility follows semantic versioning rules."
Expand All @@ -31,7 +31,7 @@
]
}
},
"lastUpdated": "2025-10-29",
"lastUpdated": "2025-10-31",
"baseUrl": "/schemas/v1",
"schemas": {
"core": {
Expand Down Expand Up @@ -135,7 +135,7 @@
},
"webhook-payload": {
"$ref": "/schemas/v1/core/webhook-payload.json",
"description": "Webhook payload structure sent when async task status changes - combines protocol-level task metadata (task_id, task_type, domain, status, timestamps) with domain-specific response data"
"description": "Webhook payload structure sent when async task status changes - protocol-level fields at top-level (operation_id, task_type, status, etc.) and task-specific payload nested under 'result'"
}
}
},
Expand Down
8 changes: 4 additions & 4 deletions schemas/v1/index.json.meta
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"etag": "W/\"6901f913-4dd6\"",
"last-modified": "Wed, 29 Oct 2025 11:22:59 GMT",
"downloaded_at": "2025-10-29T12:14:51.932120",
"content_hash": "68833f9d6e976254013f88e06ae9578b780961f9bf57acaa28a2889921d3a16d"
"etag": "W/\"69086ad9-4dd7\"",
"last-modified": "Mon, 03 Nov 2025 08:42:01 GMT",
"downloaded_at": "2025-11-03T09:02:19.665358",
"content_hash": "6a600fbffde58979d6af0b233ca0aa5b8de19ffef8d0e25a7345e7d842f59585"
}
19 changes: 16 additions & 3 deletions src/admin/blueprints/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,27 @@ def get_targeting_values(tenant_id, key_id):
return jsonify({"error": "GAM not configured for this tenant"}), 400

# Initialize GAM adapter to query values
import os

from googleads import ad_manager, oauth2

from src.adapters.gam_inventory_discovery import GAMInventoryDiscovery

gam_client = GAMInventoryDiscovery(
network_code=adapter_config.gam_network_code,
# Create OAuth client
oauth2_client = oauth2.GoogleRefreshTokenClient(
client_id=os.environ.get("GAM_OAUTH_CLIENT_ID"),
client_secret=os.environ.get("GAM_OAUTH_CLIENT_SECRET"),
refresh_token=adapter_config.gam_refresh_token,
tenant_id=tenant_id,
)

# Create Ad Manager client
gam_ad_manager_client = ad_manager.AdManagerClient(
oauth2_client, "AdCP Sales Agent", network_code=adapter_config.gam_network_code
)

# Create inventory discovery instance
gam_client = GAMInventoryDiscovery(client=gam_ad_manager_client, tenant_id=tenant_id)

# Fetch values from GAM (max 1000 to avoid timeout)
gam_values = gam_client.discover_custom_targeting_values_for_key(key_id, max_values=1000)

Expand Down
2 changes: 1 addition & 1 deletion src/core/schemas_generated/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SCHEMA_HASH: e0e8152aa4ab03df7efc72ffa03c965f
# SCHEMA_HASH: 8ad0a9cfe4d61f0754f99f6a70266739
"""
Auto-generated Pydantic models from AdCP JSON schemas.

Expand Down
Loading