Regarding the Creating the Creative via API

25 views
Skip to first unread message

Rehaman Shaik

unread,
Jul 14, 2025, 1:48:38 AM Jul 14
to Google Ad Manager API Forum
We are unable to create a creative for our custom native template (ID: 12451018) via the API. If we include the size field, we get [CommonError.CANNOT_UPDATE @ [0].size]. If we omit the size field, we get [RequiredSizeError.REQUIRED @ [0].size]. The creative is not created in the UI. However, we can create the creative via the UI with no problem. Please advise how to create a creative for this template via the API.I have created the Order and Line item but i was unable to create the Creative , could you please help me?
for reference you can refer the below my code :
import datetime
import pytz
import uuid
from fastapi import APIRouter , HTTPException , Body , Depends , BackgroundTasks
from googleads import ad_manager
from typing import Dict , List , Any , Optional
from pydantic import BaseModel , Field
from pymongo import MongoClient
from cmsapi . utils . config import settings
from cmsapi . utils . dependencies import get_mongo_client
import logging
from fastapi . responses import JSONResponse
from bson import ObjectId
from datetime import datetime , timedelta
import os
import requests
import base64
from PIL import Image
from io import BytesIO

router = APIRouter ( prefix = "/gam" , tags =[ "Google Ad Manager Enhanced" ])
GOOGLEADS_YAML = os . path . abspath (
os . path . join ( os . path . dirname ( __file__ ), "googleads.yaml" )
)

# Enhanced Pydantic models for complete GAM support
class GeoTargeting ( BaseModel ):
targeted_locations : List [ Dict [ str , Any ]] = []
excluded_locations : List [ Dict [ str , Any ]] = []

class TechnologyTargeting ( BaseModel ):
device_categories : List [ str ] = []
operating_systems : List [ str ] = []
browsers : List [ str ] = []
mobile_carriers : List [ str ] = []

class CustomTargeting ( BaseModel ):
key_values : List [ Dict [ str , Any ]] = []

class Targeting ( BaseModel ):
geo_targeting : Optional [ GeoTargeting ] = None
technology_targeting : Optional [ TechnologyTargeting ] = None
custom_targeting : Optional [ CustomTargeting ] = None
ad_unit_targeting : List [ str ] = []

class CreativeSize ( BaseModel ):
width : str
height : str
is_aspect_ratio : bool = False

class CreativePlaceholder ( BaseModel ):
size : CreativeSize
expected_creative_count : int = 1

class PrimaryGoal ( BaseModel ):
units : str
unit_type : str = "IMPRESSIONS" # IMPRESSIONS, CLICKS, VIEWABLE_IMPRESSIONS, COMPLETED_VIEWS
goal_type : str = "LIFETIME" # LIFETIME, DAILY

class Money ( BaseModel ):
currency_code : str = "INR"
micro_amount : str

class EnhancedLineItemConfig ( BaseModel ):
name : str
line_item_type : str = "STANDARD"
priority : int = Field ( 8 , ge = 1 , le = 16 )
cost_type : str = "CPM"
cost_per_unit : Money
primary_goal : PrimaryGoal
creative_placeholders : List [ CreativePlaceholder ]
start_date_time_type : str = "IMMEDIATELY"
end_date_time : Optional [ datetime ] = None
unlimited_end_date_time : bool = False
delivery_rate_type : str = "FRONTLOADED"
creative_rotation_type : str = "OPTIMIZED"
targeting : Optional [ Targeting ] = None
allow_overbook : bool = True
discount_type : str = "PERCENTAGE"
discount : float = 0.0
contracted_units_bought : Optional [ str ] = None
class TemplateVariable ( BaseModel ):
xsi_type : str
unique_name : str
value : str

class EnhancedCreativeConfig ( BaseModel ):
name : str
creative_type : str = "ImageCreative"
size : Optional [ CreativeSize ] = None
advertiser_id : int
destination_url : str = " https://example.com "
# For ImageCreative
primary_image_asset_url : Optional [ str ] = None
# For TemplateCreative
creative_template_id : Optional [ str ] = None
creative_template_variable_values : List [ TemplateVariable ] = []
# For VideoCreative
video_source_url : Optional [ str ] = None
duration : Optional [ str ] = None

class EnhancedOrderConfig ( BaseModel ):
name : str
advertiser_id : int
trafficker_id : int
start_date_time : Optional [ datetime ] = None
end_date_time : Optional [ datetime ] = None
notes : Optional [ str ] = None
unlimited_end_date_time : bool = False

class ComprehensiveGAMRequest ( BaseModel ):
revenue_order_id : str
mode : str = "custom" # "auto" or "custom"
order_config : Optional [ EnhancedOrderConfig ] = None
line_items : List [ EnhancedLineItemConfig ] = []
creatives : List [ EnhancedCreativeConfig ] = []
auto_create_associations : bool = True

class GAMResponse ( BaseModel ):
success : bool
message : str
data : Dict [ str , Any ]
errors : List [ str ] = []

# Native creative templates with enhanced metadata
NATIVE_TEMPLATES = {
"native_content" : {
"id" : "10004520" ,
"name" : "Native Content Ad" ,
"required_variables" : [ "Headline" , "Body" , "Calltoaction" ]
} ,
"native_app_install" : {
"id" : "10004400" ,
"name" : "Native App Install" ,
"required_variables" : [ "Headline" , "Body" , "Calltoaction" , "AppIcon" , "StarRating" ]
} ,
"native_video_content" : {
"id" : "10007040" ,
"name" : "Native Video Content" ,
"required_variables" : [ "Headline" , "Body" , "Calltoaction" , "VideoUrl" ]
} ,
"short_news" : {
"id" : "12402825" ,
"name" : "Short News Format" ,
"required_variables" : [ "Headline" , "Body" , "Source" ]
} ,
"promoted_story" : {
"id" : "12451018" ,
"name" : "Promoted Story" ,
"required_variables" : [ "Headline" , "Body" , "Calltoaction" , "Image" ]
}
}

# Comprehensive ad unit targeting options
AD_UNIT_TARGETS = {
"homepage" : { "name" : "Homepage" , "id" : "21675094086" } ,
"article_pages" : { "name" : "Article Pages" , "id" : "21675094087" } ,
"category_pages" : { "name" : "Category Pages" , "id" : "21675094088" } ,
"mobile_app" : { "name" : "Mobile App" , "id" : "21675094089" }
}

# Device targeting options
DEVICE_CATEGORIES = {
"desktop" : { "id" : "30000" , "name" : "Desktop" } ,
"smartphone" : { "id" : "30001" , "name" : "Smartphone" } ,
"tablet" : { "id" : "30002" , "name" : "Tablet" } ,
"connected_tv" : { "id" : "30004" , "name" : "Connected TV" }
}

# Geographic targeting for India (enhanced)
INDIA_GEO_TARGETS = {
"india" : { "id" : "2356" , "name" : "India" } ,
"maharashtra" : { "id" : "21167" , "name" : "Maharashtra, India" } ,
"karnataka" : { "id" : "21166" , "name" : "Karnataka, India" } ,
"delhi" : { "id" : "1007785" , "name" : "Delhi, India" } ,
"mumbai" : { "id" : "1007786" , "name" : "Mumbai, India" } ,
"bangalore" : { "id" : "1007809" , "name" : "Bangalore, India" } ,
"hyderabad" : { "id" : "1007810" , "name" : "Hyderabad, India" } ,
"chennai" : { "id" : "1007835" , "name" : "Chennai, India" } ,
"kolkata" : { "id" : "1007836" , "name" : "Kolkata, India" }
}

def download_and_encode_image ( image_url ):
try :
response = requests . get ( image_url , timeout = 10 ) # Add timeout to prevent hangs
response . raise_for_status () # Raise error for bad status (e.g., 404, 403)
image_data = response . content
# Validate it's a real image and get dimensions
try :
img = Image . open ( BytesIO ( image_data ))
img . verify () # Verifies if it's a valid image
width , height = img . size
logging . info ( f "Downloaded valid image: { width } x { height } " )
except Exception as e :
raise ValueError ( f "Invalid image format or corrupted: { str ( e ) } " )
# Ensure it's JPEG/PNG/GIF (GAM supported)
if response . headers . get ( 'Content-Type' ) not in [ 'image/jpeg' , 'image/png' , 'image/gif' ] :
raise ValueError ( "Unsupported image type. Must be JPEG, PNG, or GIF." )
encoded_data = base64 . b64encode ( image_data ). decode ( 'utf-8' )
file_name = image_url . split ( "/" )[- 1 ]
return encoded_data , file_name , ( width , height ) # Return actual dimensions
except requests . RequestException as e :
raise ValueError ( f "Failed to download image from { image_url } : { str ( e ) } " )
def get_ad_manager_client ():
"""Initialize and return the Google Ad Manager client."""
if not os . path . exists ( GOOGLEADS_YAML ) :
raise HTTPException ( 500 , f "googleads.yaml not found at { GOOGLEADS_YAML } " )
try :
return ad_manager . AdManagerClient . LoadFromStorage ( GOOGLEADS_YAML )
except Exception as e :
raise HTTPException ( 500 , f "GAM client init failed: { e } " )

def to_gam_datetime ( dt_input ) -> Dict [ str , Any ] :
"""Convert datetime input into the dict format GAM expects."""
try :
if isinstance ( dt_input , str ) :
if dt_input . endswith ( 'Z' ) :
dt_input = dt_input . replace ( 'Z' , '+00:00' )
dt = datetime . datetime . fromisoformat ( dt_input )
else :
dt = dt_input
tz = pytz . timezone ( "Asia/Kolkata" )
dt = dt . astimezone ( tz ) if dt . tzinfo else tz . localize ( dt )
return {
"date" : { "year" : dt . year , "month" : dt . month , "day" : dt . day } ,
"hour" : dt . hour ,
"minute" : dt . minute ,
"second" : dt . second ,
"timeZoneId" : "Asia/Kolkata"
}
except Exception as e :
raise HTTPException ( 400 , f "Invalid datetime ' { dt_input } ': { e } " )

async def get_revenue_order_from_db ( revenue_order_id : str , db_client : MongoClient ) -> Dict [ str , Any ] :
"""Fetch revenue order from MongoDB."""
db = db_client [ settings . DATABASE_NAME ]
revenue_collection = db [ settings . REVENUE_COLLECTION ]
revenue_order = revenue_collection . find_one ( { "order_id" : int ( revenue_order_id ) } )
if not revenue_order :
raise HTTPException ( 404 , f "Revenue order { revenue_order_id } not found" )
revenue_order [ "_id" ] = str ( revenue_order [ "_id" ])
return revenue_order

def find_or_create_advertiser ( client , advertiser_name : str ) -> int :
"""Find an existing advertiser by name or create a new one."""
svc = client . GetService ( "CompanyService" , version = "v202411" )
# Search for existing advertiser
stmt = (
ad_manager . StatementBuilder ( version = "v202411" )
. Where ( "name = :name AND type = :type" )
. WithBindVariable ( "name" , advertiser_name )
. WithBindVariable ( "type" , "ADVERTISER" )
. Limit ( 1 )
)
resp = svc . getCompaniesByStatement ( stmt . ToStatement ())
companies = getattr ( resp , "results" , [])
if companies :
return companies [ 0 ]. id
# Create new advertiser
new_adv = { "name" : advertiser_name , "type" : "ADVERTISER" }
created = svc . createCompanies ([ new_adv ])[ 0 ]
return created . id

def get_current_user_id ( client ) -> int :
"""Get the current user's ID to use as trafficker."""
try :
user_service = client . GetService ( "UserService" , version = "v202411" )
response = user_service . getCurrentUser ()
return response . id
except Exception as e :
raise HTTPException ( 500 , f "Failed to get current user: { e } " )

def create_enhanced_gam_order ( client , order_config : EnhancedOrderConfig ) -> Dict [ str , Any ] :
"""Create GAM order with comprehensive configuration."""
svc = client . GetService ( "OrderService" , version = "v202411" )
payload = {
"name" : order_config . name ,
"advertiserId" : order_config . advertiser_id ,
"traffickerId" : order_config . trafficker_id ,
"startDateTime" : to_gam_datetime ( order_config . start_date_time ),
"endDateTime" : to_gam_datetime ( order_config . end_date_time ),
"notes" : order_config . notes ,
"unlimitedEndDateTime" : order_config . unlimited_end_date_time
}
created_order = svc . createOrders ([ payload ])[ 0 ]
return {
"id" : created_order . id ,
"name" : created_order . name ,
"advertiserId" : created_order . advertiserId ,
"traffickerId" : created_order . traffickerId ,
"startDateTime" : payload [ "startDateTime" ],
"endDateTime" : payload [ "endDateTime" ],
"notes" : created_order . notes
}

def build_targeting_object ( targeting : Targeting ) -> Dict [ str , Any ] :
"""Build comprehensive targeting object for GAM."""
targeting_obj = {}
if targeting . geo_targeting :
geo_targeting = {}
if targeting . geo_targeting . targeted_locations :
geo_targeting [ "targetedLocations" ] = targeting . geo_targeting . targeted_locations
if targeting . geo_targeting . excluded_locations :
geo_targeting [ "excludedLocations" ] = targeting . geo_targeting . excluded_locations
targeting_obj [ "geoTargeting" ] = geo_targeting
if targeting . technology_targeting :
tech_targeting = {}
if targeting . technology_targeting . device_categories :
tech_targeting [ "deviceCategoryTargeting" ] = {
"targetedDeviceCategories" : [
{ "id" : cat_id } for cat_id in targeting . technology_targeting . device_categories
],
"isTargeted" : True
}
if targeting . technology_targeting . operating_systems :
tech_targeting [ "operatingSystemTargeting" ] = {
"targetedOperatingSystems" : [
{ "id" : os_id } for os_id in targeting . technology_targeting . operating_systems
],
"isTargeted" : True
}
if targeting . technology_targeting . browsers :
tech_targeting [ "browserTargeting" ] = {
"targetedBrowsers" : [
{ "id" : browser_id } for browser_id in targeting . technology_targeting . browsers
],
"isTargeted" : True
}
targeting_obj [ "technologyTargeting" ] = tech_targeting
if targeting . custom_targeting and targeting . custom_targeting . key_values :
custom_criteria = []
for kv in targeting . custom_targeting . key_values :
custom_criteria . append ( {
"xsi_type" : "CustomCriteria" ,
"keyId" : kv . get ( "key_id" ),
"valueIds" : kv . get ( "value_ids" , []),
"operator" : kv . get ( "operator" , "IS" )
} )
targeting_obj [ "customTargeting" ] = {
"children" : custom_criteria ,
"logicalOperator" : "AND"
}
if targeting . ad_unit_targeting :
targeting_obj [ "inventoryTargeting" ] = {
"targetedAdUnits" : [
{ "adUnitId" : ad_unit_id , "includeDescendants" : True }
for ad_unit_id in targeting . ad_unit_targeting
]
}
return targeting_obj

def create_enhanced_line_items ( client , order_id : int , line_items : List [ EnhancedLineItemConfig ] ) -> List [ Dict [ str , Any ]] :
"""Create line items with comprehensive configuration."""
svc = client . GetService ( "LineItemService" , version = "v202411" )
created_line_items = []
for li_config in line_items :
try :
payload = {
"name" : li_config . name ,
"orderId" : order_id ,
"lineItemType" : li_config . line_item_type ,
"priority" : li_config . priority ,
"costType" : li_config . cost_type ,
"costPerUnit" : {
"currencyCode" : li_config . cost_per_unit . currency_code ,
"microAmount" : li_config . cost_per_unit . micro_amount
} ,
"primaryGoal" : {
"units" : li_config . primary_goal . units ,
"unitType" : li_config . primary_goal . unit_type ,
"goalType" : li_config . primary_goal . goal_type
} ,
"creativePlaceholders" : [
{
"size" : {
"width" : cp . size . width ,
"height" : cp . size . height ,
"isAspectRatio" : cp . size . is_aspect_ratio
} ,
"expectedCreativeCount" : cp . expected_creative_count
}
for cp in li_config . creative_placeholders
],
"startDateTimeType" : li_config . start_date_time_type ,
"deliveryRateType" : li_config . delivery_rate_type ,
"creativeRotationType" : li_config . creative_rotation_type ,
"allowOverbook" : li_config . allow_overbook ,
"discountType" : li_config . discount_type ,
"discount" : li_config . discount
}
# Add end date time if specified
if li_config . end_date_time and not li_config . unlimited_end_date_time :
payload [ "endDateTime" ] = to_gam_datetime ( li_config . end_date_time )
if li_config . unlimited_end_date_time :
payload [ "unlimitedEndDateTime" ] = True
# Add contracted units if specified
if li_config . contracted_units_bought :
payload [ "contractedUnitsBought" ] = li_config . contracted_units_bought
# Add targeting if specified
if li_config . targeting :
payload [ "targeting" ] = build_targeting_object ( li_config . targeting )
created_li = svc . createLineItems ([ payload ])[ 0 ]
created_line_items . append ( {
"id" : created_li . id ,
"name" : created_li . name ,
"orderId" : created_li . orderId ,
"priority" : created_li . priority ,
"lineItemType" : created_li . lineItemType ,
"status" : created_li . status ,
"costType" : created_li . costType ,
"costPerUnit" : li_config . cost_per_unit . micro_amount ,
"goalUnits" : li_config . primary_goal . units
} )
except Exception as e :
logging . error ( f "Failed to create line item { li_config . name } : { e } " )
continue
return created_line_items

def create_enhanced_creatives ( client , creatives ):
"""
Create creatives with comprehensive configuration for Native (TemplateCreative).
"""
svc = client . GetService ( "CreativeService" , version = "v202411" )
created_creatives = []

for cr_config in creatives :
try :
if cr_config . creative_type == "TemplateCreative" :
# DO NOT include 'size' field for TemplateCreative
base_payload = {
"name" : cr_config . name ,
"advertiserId" : cr_config . advertiser_id ,
"xsi_type" : "TemplateCreative" ,
"destinationUrl" : cr_config . destination_url ,
"creativeTemplateId" : cr_config . creative_template_id ,
"creativeTemplateVariableValues" : [
{
"xsi_type" : var . xsi_type ,
"uniqueName" : var . unique_name ,
"value" : var . value
}
for var in cr_config . creative_template_variable_values
]
}
logging . info ( f "Creating native creative with payload: { base_payload } " )
created_cr = svc . createCreatives ([ base_payload ])[ 0 ]
created_creatives . append ( {
"id" : created_cr . id ,
"name" : created_cr . name ,
"advertiserId" : created_cr . advertiserId ,
"size" : "native" ,
"type" : cr_config . creative_type
} )
elif cr_config . creative_type == "ImageCreative" :
# For ImageCreative, size IS required
base_payload = {
"name" : cr_config . name ,
"advertiserId" : cr_config . advertiser_id ,
"xsi_type" : "ImageCreative" ,
"destinationUrl" : cr_config . destination_url ,
"size" : {
"width" : int ( cr_config . size . width ),
"height" : int ( cr_config . size . height ),
"isAspectRatio" : cr_config . size . is_aspect_ratio
}
}
if cr_config . primary_image_asset_url:
try :
encoded_data , file_name , dimensions = download_and_encode_image (
cr_config . primary_image_asset_url
)
base_payload [ "primaryImageAsset" ] = {
"assetByteArray" : encoded_data ,
"fileName" : file_name
}
except Exception as e :
logging . error ( f "Failed to process image: { e } " )
continue
logging . info ( f "Creating image creative with payload keys: { list ( base_payload . keys ()) } " )
created_cr = svc . createCreatives ([ base_payload ])[ 0 ]
created_creatives . append ( {
"id" : created_cr . id ,
"name" : created_cr . name ,
"advertiserId" : created_cr . advertiserId ,
"size" : f " { cr_config . size . width } x { cr_config . size . height } " ,
"type" : cr_config . creative_type
} )
else :
raise ValueError ( f "Unsupported creative type: { cr_config . creative_type } " )
except Exception as e :
logging . error ( f "Failed to create creative { cr_config . name } : { e } " )
continue

return created_creatives
def create_line_item_creative_associations ( client , line_items : List [ Dict [ str , Any ]] , creatives : List [ Dict [ str , Any ]] ) -> List [ Dict [ str , Any ]] :
"""Create associations between line items and creatives."""
svc = client . GetService ( "LineItemCreativeAssociationService" , version = "v202411" )
associations = []
created_associations = []
# Create associations for compatible sizes
for line_item in line_items :
for creative in creatives :
# Match creatives to line items based on size compatibility or other logic
associations . append ( {
"lineItemId" : line_item [ "id" ],
"creativeId" : creative [ "id" ],
"startDateTimeType" : "IMMEDIATELY"
} )
if associations :
try :
created_licas = svc . createLineItemCreativeAssociations ( associations )
for lica in created_licas :
created_associations . append ( {
"lineItemId" : lica . lineItemId ,
"creativeId" : lica . creativeId ,
"status" : "READY"
} )
except Exception as e :
logging . error ( f "Failed to create associations: { e } " )
return created_associations

@ router . post ( "/create-comprehensive-campaign" )
async def create_comprehensive_gam_campaign (
request : ComprehensiveGAMRequest ,
background_tasks : BackgroundTasks ,
db_client : MongoClient = Depends ( get_mongo_client )
) -> GAMResponse :
errors : List [ str ] = []
try :
# Fetch the revenue order
revenue_data = await get_revenue_order_from_db ( request . revenue_order_id , db_client )
client = get_ad_manager_client ()

# ── STEP 1: Create GAM Order ────────────────────────────────────────────────
gam_order = None
order_id = None
# Always create order with provided config or default
if not request . order_config :
# Create default order config
advertiser_id = find_or_create_advertiser ( client , revenue_data [ "Advertiser" ])
trafficker_id = get_current_user_id ( client )
start_date = datetime . now ( pytz . timezone ( "Asia/Kolkata" )) + timedelta ( days = 1 )
end_date = start_date + timedelta ( days = 30 )
request . order_config = EnhancedOrderConfig (
name = f "Campaign- { revenue_data [ 'Advertiser' ] } - { revenue_data [ 'order_id' ] } " ,
advertiser_id = advertiser_id ,
trafficker_id = trafficker_id ,
start_date_time = start_date ,
end_date_time = end_date ,
notes = f "Created from Revenue Order # { revenue_data [ 'order_id' ] } "
)
# Create GAM order
gam_order = create_enhanced_gam_order ( client , request . order_config )
order_id = gam_order [ "id" ]

# ── STEP 2: Create Line Items ───────────────────────────────────────────────
created_line_items : List [ Dict [ str , Any ]] = []
# FIXED: Always create line items when provided, or create default for custom mode
if request . line_items and len ( request . line_items ) > 0 :
print ( f "Creating { len ( request . line_items ) } line items..." )
created_line_items = create_enhanced_line_items ( client , order_id , request . line_items )
elif request . mode == "custom" :
# Create default line item for custom mode if none provided
print ( "No line items provided, creating default line item..." )
default_line_item = EnhancedLineItemConfig (
name = f "LineItem- { revenue_data [ 'order_id' ] } " ,
line_item_type = "STANDARD" ,
priority = 8 ,
cost_type = "CPM" ,
cost_per_unit = Money ( currency_code = "INR" , micro_amount = "1000" ), # ₹0.001 CPM
primary_goal = PrimaryGoal ( units = "1000" , unit_type = "IMPRESSIONS" , goal_type = "LIFETIME" ),
creative_placeholders =[
CreativePlaceholder (
size = CreativeSize ( width = "300" , height = "250" , is_aspect_ratio = False ),
expected_creative_count = 1
)
],
delivery_rate_type = "FRONTLOADED" ,
creative_rotation_type = "OPTIMIZED"
)
created_line_items = create_enhanced_line_items ( client , order_id , [ default_line_item ])

# ── STEP 3: Create Creatives ────────────────────────────────────────────────
created_creatives : List [ Dict [ str , Any ]] = []
# FIXED: Always create creatives when provided, or create default for custom mode
if request . creatives and len ( request . creatives ) > 0 :
print ( f "Creating { len ( request . creatives ) } creatives..." )
# Ensure all creatives have advertiser_id set
for cr in request . creatives :
if not cr . advertiser_id :
cr . advertiser_id = request . order_config . advertiser_id
created_creatives = create_enhanced_creatives ( client , request . creatives )
elif request . mode == "custom" :
# Create default creative for custom mode if none provided
print ( "No creatives provided, creating default creative..." )
advertiser_id = request . order_config . advertiser_id
default_creative = EnhancedCreativeConfig (
name = f "Creative- { revenue_data [ 'order_id' ] } " ,
creative_type = "ImageCreative" ,
size = CreativeSize ( width = "300" , height = "250" , is_aspect_ratio = False ),
advertiser_id = advertiser_id ,
destination_url = " https://example.com "
)
created_creatives = create_enhanced_creatives ( client , [ default_creative ])

# ── STEP 4: Associate Line-Items ↔ Creatives ─────────────────────
created_associations : List [ Dict [ str , Any ]] = []
if request . auto_create_associations and created_line_items and created_creatives :
print ( f "Creating associations between { len ( created_line_items ) } line items and { len ( created_creatives ) } creatives..." )
created_associations = create_line_item_creative_associations (
client ,
created_line_items ,
created_creatives
)

print ( f "Campaign creation summary: Order= { order_id } , LineItems= { len ( created_line_items ) } , Creatives= { len ( created_creatives ) } , Associations= { len ( created_associations ) } " )

# ── STEP 5: Update Revenue Order in Database ───────────────────────────────────
gam_data = {
"gam_order_id" : order_id ,
"gam_line_item_ids" : [ li [ "id" ] for li in created_line_items ],
"gam_creative_ids" : [ cr [ "id" ] for cr in created_creatives ],
"creation_mode" : request . mode ,
"gam_created_date" : datetime . now (),
"gam_campaign_status" : "CREATED"
}
background_tasks . add_task (
update_revenue_order_with_gam_data ,
request . revenue_order_id ,
gam_data ,
db_client
)

return GAMResponse (
success = True ,
message = "Comprehensive GAM campaign created successfully" ,
data = {
"revenue_order_id" : request . revenue_order_id ,
"gam_order" : gam_order ,
"gam_order_id" : order_id ,
"line_items" : created_line_items ,
"creatives" : created_creatives ,
"associations" : created_associations ,
"summary" : {
"order_id" : order_id ,
"total_line_items" : len ( created_line_items ),
"total_creatives" : len ( created_creatives ),
"total_associations" : len ( created_associations )
}
} ,
errors = errors
)

except Exception as e :
logging . error ( f "create-comprehensive-campaign failed: { e } " )
import traceback
traceback . print_exc ()
return GAMResponse (
success = False ,
message = f "Failed to create GAM campaign: { str ( e ) } " ,
data = {} ,
errors =[ str ( e )]
)
async def update_revenue_order_with_gam_data ( order_id : str , gam_data : Dict [ str , Any ] , db_client : MongoClient ):
"""Update revenue order with GAM campaign data."""
try :
db = db_client [ settings . DATABASE_NAME ]
revenue_collection = db [ settings . REVENUE_COLLECTION ]
revenue_collection . update_one (
{ "order_id" : int ( order_id ) } ,
{ "$set" : gam_data }
)
except Exception as e :
logging . error ( f "Failed to update revenue order { order_id } with GAM data: { e } " )

# Missing endpoints that frontend expects
@ router . get ( "/revenue-order/ {revenue_order_id} /gam-status" )
async def check_gam_status (
revenue_order_id : str ,
db_client : MongoClient = Depends ( get_mongo_client )
):
"""Check if GAM campaign already exists for revenue order."""
try :
revenue_data = await get_revenue_order_from_db ( revenue_order_id , db_client )
gam_exists = bool ( revenue_data . get ( "gam_order_id" ))
return {
"success" : True ,
"revenue_order_id" : revenue_order_id ,
"gam_campaign_exists" : gam_exists ,
"gam_order_id" : revenue_data . get ( "gam_order_id" ),
"ad_type" : revenue_data . get ( "ad_type" ),
"created_date" : revenue_data . get ( "gam_created_date" ),
"campaign_status" : revenue_data . get ( "gam_campaign_status" , "NOT_CREATED" ),
"line_item_count" : len ( revenue_data . get ( "gam_line_item_ids" , [])),
"creative_count" : len ( revenue_data . get ( "gam_creative_ids" , []))
}
except Exception as e :
return {
"success" : False ,
"error" : str ( e ),
"gam_campaign_exists" : False
}

@ router . get ( "/revenue-order/ {revenue_order_id} /campaign-details" )
async def get_campaign_details (
revenue_order_id : str ,
db_client : MongoClient = Depends ( get_mongo_client )
):
"""Get detailed campaign information for editing."""
try :
revenue_data = await get_revenue_order_from_db ( revenue_order_id , db_client )
if not revenue_data . get ( "gam_order_id" ) :
return {
"success" : False ,
"message" : "No GAM campaign found for this revenue order"
}
# Initialize GAM client and fetch detailed campaign data
client = get_ad_manager_client ()
# Get order details
order_service = client . GetService ( "OrderService" , version = "v202411" )
order = order_service . getOrdersByStatement (
ad_manager . StatementBuilder ( version = "v202411" )
. Where ( "id = :orderId" )
. WithBindVariable ( "orderId" , revenue_data [ "gam_order_id" ])
. ToStatement ()
). results [ 0 ]
# Get line items
line_item_service = client . GetService ( "LineItemService" , version = "v202411" )
line_items = line_item_service . getLineItemsByStatement (
ad_manager . StatementBuilder ( version = "v202411" )
. Where ( "orderId = :orderId" )
. WithBindVariable ( "orderId" , revenue_data [ "gam_order_id" ])
. ToStatement ()
). results
# Get creatives
creative_service = client . GetService ( "CreativeService" , version = "v202411" )
creatives = creative_service . getCreativesByStatement (
ad_manager . StatementBuilder ( version = "v202411" )
. Where ( "advertiserId = :advertiserId" )
. WithBindVariable ( "advertiserId" , order . advertiserId )
. ToStatement ()
). results
return {
"success" : True ,
"data" : {
"order" : {
"id" : order . id ,
"name" : order . name ,
"advertiserId" : order . advertiserId ,
"startDateTime" : order . startDateTime ,
"endDateTime" : order . endDateTime ,
"notes" : order . notes
} ,
"lineItems" : [
{
"id" : li . id ,
"name" : li . name ,
"priority" : li . priority ,
"lineItemType" : li . lineItemType ,
"costType" : li . costType ,
"status" : li . status ,
"budget" : li . costPerUnit . microAmount if li . costPerUnit else 0 ,
"goalUnits" : li . primaryGoal . units if li . primaryGoal else 0
}
for li in line_items
],
"creatives" : [
{
"id" : cr . id ,
"name" : cr . name ,
"size" : f " { cr . size . width } x { cr . size . height } " if cr . size else "N/A"
}
for cr in creatives
]
}
}
except Exception as e :
return {
"success" : False ,
"error" : str ( e )
}

# Add the endpoint with the name frontend expects
@ router . post ( "/auto-create-campaign" )
async def auto_create_campaign_alias (
request : ComprehensiveGAMRequest ,
background_tasks : BackgroundTasks ,
db_client : MongoClient = Depends ( get_mongo_client )
) -> GAMResponse :
"""Alias endpoint for frontend compatibility."""
return await create_comprehensive_gam_campaign ( request , background_tasks , db_client )

@ router . post ( "/orders/ {order_id} /line-items" )
async def create_line_items_for_order (
order_id : int ,
items : List [ EnhancedLineItemConfig ] ,
db_client : MongoClient = Depends ( get_mongo_client )
):
client = get_ad_manager_client ()
created = create_enhanced_line_items ( client , order_id , items )
return { "success" : True , "created_line_items" : created }

@ router . post ( "/orders/ {order_id} /creatives" )
async def create_creatives_for_order (
order_id : int ,
creatives : List [ EnhancedCreativeConfig ] ,
db_client : MongoClient = Depends ( get_mongo_client )
):
client = get_ad_manager_client ()
# ensure each creative.advertiser_id is set to order's advertiser
created = create_enhanced_creatives ( client , creatives )
return { "success" : True , "created_creatives" : created }

@ router . post ( "/orders/ {order_id} /associations" )
async def associate_line_items_and_creatives (
order_id : int ,
payload : Dict [ str , List [ int ]] , # { lineItemIds: [...], creativeIds: [...] }
db_client : MongoClient = Depends ( get_mongo_client )
):
client = get_ad_manager_client ()
line_items = payload . get ( "lineItemIds" , [])
creatives = payload . get ( "creativeIds" , [])
assoc = create_line_item_creative_associations (
client ,
[ { "id" : id } for id in line_items ],
[ { "id" : id } for id in creatives ]
)
return { "success" : True , "created_associations" : assoc }
# Utility endpoints for configuration data
@ router . get ( "/config/native-templates" )
async def get_native_templates ():
"""Get available native creative templates with metadata."""
return {
"success" : True ,
"templates" : NATIVE_TEMPLATES
}

@ router . get ( "/config/device-categories" )
async def get_device_categories ():
"""Get available device categories for targeting."""
return {
"success" : True ,
"device_categories" : DEVICE_CATEGORIES
}

@ router . get ( "/config/geo-targets" )
async def get_geo_targets ():
"""Get available geographic targets for India."""
return {
"success" : True ,
"geo_targets" : INDIA_GEO_TARGETS
}

@ router . get ( "/config/ad-units" )
async def get_ad_units ():
"""Get available ad unit targets."""
return {
"success" : True ,
"ad_units" : AD_UNIT_TARGETS
}

@ router . get ( "/orders" )
def list_orders ( limit : int = 10 ):
"""List Orders with enhanced details"""
try :
client = get_ad_manager_client ()
order_service = client . GetService ( "OrderService" , version = "v202411" )
statement = ad_manager . StatementBuilder ( version = "v202411" ). Limit ( limit )
response = order_service . getOrdersByStatement ( statement . ToStatement ())
orders = getattr ( response , "results" , [])
return { "success" : True , "orders" : [ o .__dict__ for o in orders ] }
except Exception as e :
return { "success" : False , "error" : str ( e ) }

@ router . get ( "/line-items" )
def list_line_items ( limit : int = 10 ):
"""List Line Items with enhanced details"""
try :
client = get_ad_manager_client ()
line_item_service = client . GetService ( "LineItemService" , version = "v202411" )
statement = ad_manager . StatementBuilder ( version = "v202411" ). Limit ( limit )
response = line_item_service . getLineItemsByStatement ( statement . ToStatement ())
items = getattr ( response , "results" , [])
return { "success" : True , "line_items" : [ i .__dict__ for i in items ] }
except Exception as e :
return { "success" : False , "error" : str ( e ) }

@ router . get ( "/creatives" )
def list_creatives ( limit : int = 10 ):
"""List Creatives with enhanced details"""
try :
client = get_ad_manager_client ()
creative_service = client . GetService ( "CreativeService" , version = "v202411" )
statement = ad_manager . StatementBuilder ( version = "v202411" ). Limit ( limit )
response = creative_service . getCreativesByStatement ( statement . ToStatement ())
creatives = getattr ( response , "results" , [])
return { "success" : True , "creatives" : [ c .__dict__ for c in creatives ] }
except Exception as e :
return { "success" : False , "error" : str ( e ) }

@ router . get ( "/users" )
def list_users ( limit : int = 50 ):
"""List GAM Users for trafficker selection"""
try :
client = get_ad_manager_client ()
user_service = client . GetService ( "UserService" , version = "v202411" )
statement = ad_manager . StatementBuilder ( version = "v202411" ). Limit ( limit )
response = user_service . getUsersByStatement ( statement . ToStatement ())
users = getattr ( response , "results" , [])
return { "success" : True , "users" : [ u .__dict__ for u in users ] }
except Exception as e :
return { "success" : False , "error" : str ( e ) }
@ router . get ( "/advertisers" )
def list_advertisers ( limit : int = 50 , offset : int = 0 ):
"""
List all advertisers in Google Ad Manager
"""
try :
client = get_ad_manager_client ()
company_service = client . GetService ( "CompanyService" , version = "v202411" )
# Build statement to fetch advertisers
statement = (
ad_manager . StatementBuilder ( version = "v202411" )
. Where ( "type = :type" )
. WithBindVariable ( "type" , "ADVERTISER" )
. Limit ( limit )
. Offset ( offset )
)

# Fetch advertisers
response = company_service . getCompaniesByStatement ( statement . ToStatement ())
advertisers = getattr ( response , "results" , [])

return {
"success" : True ,
"advertisers" : [
{
"id" : adv . id ,
"name" : adv . name ,
"type" : adv . type ,
"creditStatus" : adv . creditStatus
}
for adv in advertisers
],
"totalCount" : response . totalResultSetSize
}
except Exception as e :
logging . error ( f "Failed to fetch advertisers: { e } " )
return { "success" : False , "error" : str ( e ) }

@ router . post ( "/line-items" )
def create_line_item (
line_item : EnhancedLineItemConfig ,
db_client : MongoClient = Depends ( get_mongo_client )
):
try :
db = db_client [ settings . DATABASE_NAME ]
line_item_data = line_item . dict ()
line_item_data [ 'created_at' ] = datetime . utcnow ()
line_item_data [ 'source' ] = 'API'
line_item_data [ 'id' ] = str ( ObjectId ())
db [ 'line_items' ]. insert_one ( line_item_data )
return JSONResponse ( {
'success' : True ,
'local_id' : line_item_data [ 'id' ],
'message' : 'Line item created locally'
} )
except Exception as e :
raise HTTPException ( status_code = 500 , detail = f "Error creating line item: { str ( e ) } " )
Reply all
Reply to author
Forward
0 new messages