diff --git a/HISTORY.rst b/HISTORY.rst
index eb273b40..b4d138ca 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -2,6 +2,14 @@
Release History
+13.0.24(2025-02-20)
++++++++++++++++++++++++++
+* Update Bing Ads API Version 13 service proxies to reflect recent interface changes. For details please see the Bing Ads API Release Notes: https://learn.microsoft.com/en-us/advertising/guides/release-notes?view=bingads-13.
+* Added NewCustomerAcquisitionGoalSetting in BulkCampaign mapping.
+* Added SubType, ActionType in BulkCampaignConversionGoal mapping.
+* Added CampaignId in BulkKeyword mapping.
+* Added bulk mappings for NCA: BulkNewCustomerAcquisitionGoal.
+
13.0.23.1(2025-01-23)
+++++++++++++++++++++++++
* Update Bing Ads API Version 13 service proxies to reflect recent interface changes. For details please see the Bing Ads API Release Notes: https://learn.microsoft.com/en-us/advertising/guides/release-notes?view=bingads-13.
diff --git a/bingads/manifest.py b/bingads/manifest.py
index b5c38812..28bd4ee8 100644
--- a/bingads/manifest.py
+++ b/bingads/manifest.py
@@ -1,5 +1,5 @@
import sys
-VERSION = '13.0.23.1'
+VERSION = '13.0.24'
BULK_FORMAT_VERSION_6 = '6.0'
WORKING_NAME = 'BingAdsSDKPython'
USER_AGENT = '{0} {1} {2}'.format(WORKING_NAME, VERSION, sys.version_info[0:3])
diff --git a/bingads/v13/bulk/entities/__init__.py b/bingads/v13/bulk/entities/__init__.py
index 36cd5a06..4ed27c99 100644
--- a/bingads/v13/bulk/entities/__init__.py
+++ b/bingads/v13/bulk/entities/__init__.py
@@ -55,4 +55,5 @@
from .bulk_campaign_brand_list_association import *
from .bulk_brand_item import *
from .bulk_brand_list import *
+from .bulk_new_customer_acquisition_goal import *
from .goals import *
diff --git a/bingads/v13/bulk/entities/bulk_budget.py b/bingads/v13/bulk/entities/bulk_budget.py
index a9c585e7..91c49778 100644
--- a/bingads/v13/bulk/entities/bulk_budget.py
+++ b/bingads/v13/bulk/entities/bulk_budget.py
@@ -20,11 +20,12 @@ class BulkBudget(_SingleRecordBulkEntity):
* :class:`.BulkFileWriter`
"""
- def __init__(self, budget=None, status=None, account_id=None):
+ def __init__(self, budget=None, status=None, account_id=None, campaign_id=None):
super(BulkBudget, self).__init__()
self._budget = budget
self._status = status
self._account_id = account_id
+ self._campaign_id = campaign_id
@property
def budget(self):
@@ -63,6 +64,19 @@ def account_id(self):
def account_id(self, value):
self._account_id = value
+ @property
+ def campaign_id(self):
+ """ the id of the campaign which contains the budget
+ Corresponds to the 'Campaign Id' field in the bulk file.
+
+ :rtype: long
+ """
+ return self._campaign_id
+
+ @campaign_id.setter
+ def campaign_id(self, value):
+ self._campaign_id = value
+
_MAPPINGS = [
_SimpleBulkMapping(
@@ -75,6 +89,11 @@ def account_id(self, value):
field_to_csv=lambda c: bulk_str(c.account_id),
csv_to_field=lambda c, v: setattr(c, 'account_id', int(v) if v else None)
),
+ _SimpleBulkMapping(
+ header=_StringTable.CampaignId,
+ field_to_csv=lambda c: bulk_str(c.campaign_id),
+ csv_to_field=lambda c, v: setattr(c, 'campaign_id', int(v) if v else None)
+ ),
_SimpleBulkMapping(
header=_StringTable.Status,
field_to_csv=lambda c: c.status,
diff --git a/bingads/v13/bulk/entities/bulk_campaign.py b/bingads/v13/bulk/entities/bulk_campaign.py
index e9d9bfcb..991474b5 100644
--- a/bingads/v13/bulk/entities/bulk_campaign.py
+++ b/bingads/v13/bulk/entities/bulk_campaign.py
@@ -4,6 +4,7 @@
from bingads.v13.internal.bulk.entities.single_record_bulk_entity import _SingleRecordBulkEntity
from bingads.v13.internal.bulk.mappings import _SimpleBulkMapping, _ComplexBulkMapping
from bingads.v13.internal.extensions import *
+from decimal import Decimal
_DynamicFeedSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('DynamicFeedSetting'))
_TargetSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('TargetSetting'))
@@ -12,6 +13,7 @@
_DisclaimerSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('DisclaimerSetting'))
_VerifiedTrackingSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('VerifiedTrackingSetting'))
_PerformanceMaxSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('PerformanceMaxSetting'))
+_NewCustomerAcquisitionGoalSetting = type(_CAMPAIGN_OBJECT_FACTORY_V13.create('NewCustomerAcquisitionGoalSetting'))
class BulkCampaign(_SingleRecordBulkEntity):
""" Represents a campaign that can be read or written in a bulk file.
@@ -167,6 +169,9 @@ def _get_verified_tracking_setting(self):
def _get_performance_max_setting(self):
return self._get_setting(_PerformanceMaxSetting, 'PerformanceMaxSetting')
+ def _get_new_customer_acquisition_goal_setting(self):
+ return self._get_setting(_NewCustomerAcquisitionGoalSetting, 'NewCustomerAcquisitionGoalSetting')
+
def _get_setting(self, setting_type, setting_name):
if not self.campaign.Settings.Setting:
return None
@@ -205,6 +210,7 @@ def _read_campaign_type(c, v):
if campaign_type.lower() == 'performancemax':
BulkCampaign._create_campaign_setting(c.campaign, 'PerformanceMaxSetting')
BulkCampaign._create_campaign_setting(c.campaign, 'ShoppingSetting')
+ BulkCampaign._create_campaign_setting(c.campaign, 'NewCustomerAcquisitionGoalSetting')
@staticmethod
def _create_campaign_setting(campaign, setting_type):
@@ -521,6 +527,72 @@ def _write_image_opt_out(c):
return None
return bulk_str(performance_max_setting.AutoGeneratedImageOptOut)
+ @staticmethod
+ def _read_new_customer_acquisition_bid_only_mode(c, v):
+ if not c.campaign.CampaignType:
+ return None
+ campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType]
+ if 'performancemax' in campgaign_types:
+ new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting()
+ if not new_customer_acquisition_goal_setting:
+ return None
+ new_customer_acquisition_goal_setting.NewCustomerAcquisitionBidOnlyMode = parse_bool(v)
+
+ @staticmethod
+ def _write_new_customer_acquisition_bid_only_mode(c):
+ if not c.campaign.CampaignType:
+ return None
+ campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType]
+ if 'performancemax' in campgaign_types:
+ new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting()
+ if not new_customer_acquisition_goal_setting:
+ return None
+ return bulk_str(new_customer_acquisition_goal_setting.NewCustomerAcquisitionBidOnlyMode)
+
+ @staticmethod
+ def _read_new_customer_acquisition_goal_id(c, v):
+ if not c.campaign.CampaignType:
+ return None
+ campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType]
+ if 'performancemax' in campgaign_types:
+ new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting()
+ if not new_customer_acquisition_goal_setting:
+ return None
+ new_customer_acquisition_goal_setting.NewCustomerAcquisitionGoalId = int(v) if v else None
+
+ @staticmethod
+ def _write_new_customer_acquisition_goal_id(c):
+ if not c.campaign.CampaignType:
+ return None
+ campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType]
+ if 'performancemax' in campgaign_types:
+ new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting()
+ if not new_customer_acquisition_goal_setting:
+ return None
+ return bulk_str(new_customer_acquisition_goal_setting.NewCustomerAcquisitionGoalId)
+
+ @staticmethod
+ def _read_additional_conversion_value(c, v):
+ if not c.campaign.CampaignType:
+ return None
+ campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType]
+ if 'performancemax' in campgaign_types:
+ new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting()
+ if not new_customer_acquisition_goal_setting:
+ return None
+ new_customer_acquisition_goal_setting.AdditionalConversionValue = Decimal(v) if v else None
+
+ @staticmethod
+ def _write_additional_conversion_value(c):
+ if not c.campaign.CampaignType:
+ return None
+ campgaign_types = [campaign_type.lower() for campaign_type in c.campaign.CampaignType]
+ if 'performancemax' in campgaign_types:
+ new_customer_acquisition_goal_setting = c._get_new_customer_acquisition_goal_setting()
+ if not new_customer_acquisition_goal_setting:
+ return None
+ return bulk_str(new_customer_acquisition_goal_setting.AdditionalConversionValue)
+
@staticmethod
def _read_website(c, v):
if not c.campaign.CampaignType:
@@ -756,6 +828,21 @@ def _write_website(c):
field_to_csv=lambda c: field_to_csv_bool(c.should_serve_on_msan),
csv_to_field=lambda c, v: setattr(c, 'should_serve_on_msan', parse_bool(v))
),
+ _SimpleBulkMapping(
+ header=_StringTable.NewCustomerAcquisitionGoalId,
+ field_to_csv=lambda c: BulkCampaign._write_new_customer_acquisition_goal_id(c),
+ csv_to_field=lambda c, v: BulkCampaign._read_new_customer_acquisition_goal_id(c, v)
+ ),
+ _SimpleBulkMapping(
+ header=_StringTable.NewCustomerAcquisitionBidOnlyMode,
+ field_to_csv=lambda c: BulkCampaign._write_new_customer_acquisition_bid_only_mode(c),
+ csv_to_field=lambda c, v: BulkCampaign._read_new_customer_acquisition_bid_only_mode(c, v)
+ ),
+ _SimpleBulkMapping(
+ header=_StringTable.AdditionalConversionValue,
+ field_to_csv=lambda c: BulkCampaign._write_additional_conversion_value(c),
+ csv_to_field=lambda c, v: BulkCampaign._read_additional_conversion_value(c, v)
+ ),
]
def read_additional_data(self, stream_reader):
diff --git a/bingads/v13/bulk/entities/bulk_campaign_conversion_goal.py b/bingads/v13/bulk/entities/bulk_campaign_conversion_goal.py
index 7900c52c..0ae398fc 100644
--- a/bingads/v13/bulk/entities/bulk_campaign_conversion_goal.py
+++ b/bingads/v13/bulk/entities/bulk_campaign_conversion_goal.py
@@ -10,7 +10,7 @@ class BulkCampaignConversionGoal(_SingleRecordBulkEntity):
Properties of this class and of classes that it is derived from, correspond to fields of the CampaignConversionGoal record in a bulk file.
For more information, see CampaignConversionGoal at https://go.microsoft.com/fwlink/?linkid=846127
-
+
*See also:*
* :class:`.BulkServiceManager`
@@ -19,10 +19,11 @@ class BulkCampaignConversionGoal(_SingleRecordBulkEntity):
* :class:`.BulkFileWriter`
"""
- def __init__(self, campaign_conversion_goal = None):
+ def __init__(self, campaign_conversion_goal = None, sub_type = None, action_type = None):
super(BulkCampaignConversionGoal, self).__init__()
self._campaign_conversion_goal = campaign_conversion_goal
-
+ self._sub_type = sub_type
+ self._action_type = action_type
@property
def campaign_conversion_goal(self):
@@ -34,7 +35,31 @@ def campaign_conversion_goal(self):
@campaign_conversion_goal.setter
def campaign_conversion_goal(self, value):
- self._campaign_conversion_goal = value
+ self._campaign_conversion_goal = value
+
+ @property
+ def sub_type(self):
+ """ Corresponds to the 'Sub Type' field in the bulk file.
+
+ :rtype: str
+ """
+ return self._sub_type
+
+ @sub_type.setter
+ def sub_type(self, value):
+ self._sub_type = value
+
+ @property
+ def action_type(self):
+ """ Corresponds to the 'Action Type' field in the bulk file.
+
+ :rtype: str
+ """
+ return self._action_type
+
+ @action_type.setter
+ def action_type(self, value):
+ self._action_type = value
_MAPPINGS = [
@@ -48,6 +73,16 @@ def campaign_conversion_goal(self, value):
field_to_csv=lambda c: bulk_str(c.campaign_conversion_goal.GoalId),
csv_to_field=lambda c, v: setattr(c.campaign_conversion_goal, 'GoalId', int(v) if v else None)
),
+ _SimpleBulkMapping(
+ header=_StringTable.ActionType,
+ field_to_csv=lambda c: c.action_type,
+ csv_to_field=lambda c, v: setattr(c, 'action_type', v)
+ ),
+ _SimpleBulkMapping(
+ header=_StringTable.SubType,
+ field_to_csv=lambda c: c.sub_type,
+ csv_to_field=lambda c, v: setattr(c, 'sub_type', v)
+ ),
]
def process_mappings_from_row_values(self, row_values):
diff --git a/bingads/v13/bulk/entities/bulk_new_customer_acquisition_goal.py b/bingads/v13/bulk/entities/bulk_new_customer_acquisition_goal.py
new file mode 100644
index 00000000..816aed2c
--- /dev/null
+++ b/bingads/v13/bulk/entities/bulk_new_customer_acquisition_goal.py
@@ -0,0 +1,81 @@
+from bingads.service_client import _CAMPAIGN_OBJECT_FACTORY_V13
+from bingads.v13.internal.bulk.string_table import _StringTable
+from bingads.v13.internal.bulk.entities.single_record_bulk_entity import _SingleRecordBulkEntity
+from bingads.v13.internal.bulk.mappings import _SimpleBulkMapping
+from bingads.v13.internal.extensions import *
+from decimal import Decimal
+
+
+class BulkNewCustomerAcquisitionGoal (_SingleRecordBulkEntity):
+ """ Represents a new customer acquisition goal that can be read or written in a bulk file.
+
+ Properties of this class and of classes that it is derived from, correspond to fields of the Budget record in a bulk file.
+ For more information, see Budget at https://go.microsoft.com/fwlink/?linkid=846127
+
+ *See also:*
+
+ * :class:`.BulkServiceManager`
+ * :class:`.BulkOperation`
+ * :class:`.BulkFileReader`
+ * :class:`.BulkFileWriter`
+ """
+
+ def __init__(self, new_customer_acquisition_goal=None, target=None):
+ super(BulkNewCustomerAcquisitionGoal , self).__init__()
+ self._new_customer_acquisition_goal = new_customer_acquisition_goal
+ self._target = target
+
+ @property
+ def new_customer_acquisition_goal (self):
+ """
+ the NewCustomerAcquisitionGoal object, see more detail at: https://go.microsoft.com/fwlink/?linkid=846127
+ """
+ return self._new_customer_acquisition_goal
+
+ @new_customer_acquisition_goal .setter
+ def new_customer_acquisition_goal (self, value):
+ self._new_customer_acquisition_goal = value
+
+ @property
+ def target(self):
+ """
+ The ids of audiences within the new customer acquisition.
+ It should be split by simicolon. example: "123;456;789"
+ Corresponds to 'Target' field in bulk file.
+ :rtype: str
+ """
+ return self._target
+
+ @target.setter
+ def target(self, value):
+ self._target = value
+
+
+ _MAPPINGS = [
+ _SimpleBulkMapping(
+ header=_StringTable.Id,
+ field_to_csv=lambda c: bulk_str(c.new_customer_acquisition_goal .Id),
+ csv_to_field=lambda c, v: setattr(c.new_customer_acquisition_goal , 'Id', int(v) if v else None)
+ ),
+ _SimpleBulkMapping(
+ header=_StringTable.Target,
+ field_to_csv=lambda c: bulk_str(c.target),
+ csv_to_field=lambda c, v: setattr(c, 'target', v)
+ ),
+ _SimpleBulkMapping(
+ header=_StringTable.AdditionalConversionValue,
+ field_to_csv=lambda c: bulk_str(c.new_customer_acquisition_goal.AdditionalValue),
+ csv_to_field=lambda c, v: setattr(c.new_customer_acquisition_goal , 'AdditionalValue', Decimal(v) if v else None)
+ ),
+ ]
+
+ def process_mappings_from_row_values(self, row_values):
+ self._new_customer_acquisition_goal = _CAMPAIGN_OBJECT_FACTORY_V13.create('NewCustomerAcquisitionGoal')
+ row_values.convert_to_entity(self, BulkNewCustomerAcquisitionGoal ._MAPPINGS)
+
+ def process_mappings_to_row_values(self, row_values, exclude_readonly_data):
+ self._validate_property_not_null(self.new_customer_acquisition_goal , 'new_customer_acquisition_goal ')
+ self.convert_to_values(row_values, BulkNewCustomerAcquisitionGoal ._MAPPINGS)
+
+ def read_additional_data(self, stream_reader):
+ super(BulkNewCustomerAcquisitionGoal , self).read_additional_data(stream_reader)
diff --git a/bingads/v13/internal/bulk/bulk_object_factory.py b/bingads/v13/internal/bulk/bulk_object_factory.py
index ca0a4c30..8cd63936 100644
--- a/bingads/v13/internal/bulk/bulk_object_factory.py
+++ b/bingads/v13/internal/bulk/bulk_object_factory.py
@@ -233,6 +233,7 @@ class _BulkObjectFactory():
_StringTable.BrandItem: _EntityInfo(lambda: BulkBrandItem()),
_StringTable.CampaignBrandList: _EntityInfo(lambda: BulkCampaignBrandListAssociation()),
_StringTable.AssetGroupUrlTarget: _EntityInfo(lambda: BulkAssetGroupUrlTarget()),
+ _StringTable.NewCustomerAcquisitionGoal: _EntityInfo(lambda: BulkNewCustomerAcquisitionGoal()),
}
ADDITIONAL_OBJECT_MAP = {
diff --git a/bingads/v13/internal/bulk/csv_headers.py b/bingads/v13/internal/bulk/csv_headers.py
index e5cef573..62c040d6 100644
--- a/bingads/v13/internal/bulk/csv_headers.py
+++ b/bingads/v13/internal/bulk/csv_headers.py
@@ -8,6 +8,7 @@ class _CsvHeaders:
_StringTable.Status,
_StringTable.Id,
_StringTable.ParentId,
+ _StringTable.CampaignId,
_StringTable.SubType,
_StringTable.Campaign,
_StringTable.AdGroup,
@@ -528,6 +529,12 @@ class _CsvHeaders:
_StringTable.AssetGroupTargetValue1,
_StringTable.AssetGroupTargetValue2,
_StringTable.AssetGroupTargetValue3,
+
+ # New Customer Acquisition Goal
+ _StringTable.AdditionalConversionValue,
+ _StringTable.NewCustomerAcquisitionGoalId,
+ _StringTable.NewCustomerAcquisitionBidOnlyMode,
+
]
@staticmethod
diff --git a/bingads/v13/internal/bulk/string_table.py b/bingads/v13/internal/bulk/string_table.py
index 27a240b8..72134e26 100644
--- a/bingads/v13/internal/bulk/string_table.py
+++ b/bingads/v13/internal/bulk/string_table.py
@@ -7,6 +7,7 @@ class _StringTable:
Id = "Id"
BusinessId = "Business Id"
ParentId = "Parent Id"
+ CampaignId = "Campaign Id"
TimeZone = "Time Zone"
Budget = "Budget"
BudgetType = "Budget Type"
@@ -731,3 +732,9 @@ class _StringTable:
AssetGroupTargetValue1 = "Asset Group Target Value 1";
AssetGroupTargetValue2 = "Asset Group Target Value 2";
AssetGroupTargetValue3 = "Asset Group Target Value 3";
+
+ # New Customer Acquisition Goal
+ NewCustomerAcquisitionGoal = "New Customer Acquisition Goal";
+ AdditionalConversionValue = "Additional Conversion Value";
+ NewCustomerAcquisitionGoalId = "New Customer Acquisition Goal Id";
+ NewCustomerAcquisitionBidOnlyMode = "New Customer Acquisition Bid Only Mode";
diff --git a/bingads/v13/proxies/production/campaignmanagement_service.xml b/bingads/v13/proxies/production/campaignmanagement_service.xml
index 46fd11e8..de79c6e8 100644
--- a/bingads/v13/proxies/production/campaignmanagement_service.xml
+++ b/bingads/v13/proxies/production/campaignmanagement_service.xml
@@ -1180,6 +1180,13 @@
+
+
+
+
+
+
+
@@ -1206,11 +1213,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1604,6 +1625,7 @@
+
@@ -5092,6 +5114,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -5223,6 +5256,14 @@
+
+
+
+
+
+
+
+
@@ -8809,9 +8850,29 @@
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
@@ -8873,37 +8934,50 @@
-
-
-
+
+
+
+ 1
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
+ 3
+
+
+
-
+
-
+
-
+
-
-
+
+
-
+
+
-
-
-
-
-
-
-
+
@@ -8915,7 +8989,7 @@
-
+
@@ -8930,7 +9004,7 @@
-
+
@@ -9195,11 +9269,11 @@
-
-
+
+
-
+
@@ -9212,26 +9286,38 @@
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9296,6 +9382,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9328,6 +9545,12 @@
+
+
+
+
+
+
@@ -13480,6 +13703,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -14495,6 +14838,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18386,6 +18759,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bingads/v13/proxies/production/customermanagement_service.xml b/bingads/v13/proxies/production/customermanagement_service.xml
index 0ec8c375..a9abb138 100644
--- a/bingads/v13/proxies/production/customermanagement_service.xml
+++ b/bingads/v13/proxies/production/customermanagement_service.xml
@@ -881,6 +881,8 @@
+
+
@@ -1867,6 +1869,20 @@
+
+
+
+ 1060
+
+
+
+
+
+
+ 1062
+
+
+
diff --git a/bingads/v13/proxies/production/reporting_service.xml b/bingads/v13/proxies/production/reporting_service.xml
index 94b5fcae..cb38a40f 100644
--- a/bingads/v13/proxies/production/reporting_service.xml
+++ b/bingads/v13/proxies/production/reporting_service.xml
@@ -4438,6 +4438,8 @@
+
+
diff --git a/bingads/v13/proxies/sandbox/campaignmanagement_service.xml b/bingads/v13/proxies/sandbox/campaignmanagement_service.xml
index 1043af29..725d6028 100644
--- a/bingads/v13/proxies/sandbox/campaignmanagement_service.xml
+++ b/bingads/v13/proxies/sandbox/campaignmanagement_service.xml
@@ -1180,6 +1180,13 @@
+
+
+
+
+
+
+
@@ -1206,11 +1213,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1604,6 +1625,7 @@
+
@@ -5092,6 +5114,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -5223,6 +5256,14 @@
+
+
+
+
+
+
+
+
@@ -8809,9 +8850,29 @@
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
@@ -8873,37 +8934,50 @@
-
-
-
+
+
+
+ 1
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
+ 3
+
+
+
-
+
-
+
-
+
-
-
+
+
-
+
+
-
-
-
-
-
-
-
+
@@ -8915,7 +8989,7 @@
-
+
@@ -8930,7 +9004,7 @@
-
+
@@ -9195,11 +9269,11 @@
-
-
+
+
-
+
@@ -9212,26 +9286,38 @@
-
-
+
+
-
-
+
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9296,6 +9382,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -9328,6 +9545,12 @@
+
+
+
+
+
+
@@ -13480,6 +13703,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -14495,6 +14838,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18386,6 +18759,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bingads/v13/proxies/sandbox/customermanagement_service.xml b/bingads/v13/proxies/sandbox/customermanagement_service.xml
index 026f0ef0..a5cebe75 100644
--- a/bingads/v13/proxies/sandbox/customermanagement_service.xml
+++ b/bingads/v13/proxies/sandbox/customermanagement_service.xml
@@ -881,6 +881,8 @@
+
+
@@ -1867,6 +1869,20 @@
+
+
+
+ 1060
+
+
+
+
+
+
+ 1062
+
+
+
diff --git a/bingads/v13/proxies/sandbox/reporting_service.xml b/bingads/v13/proxies/sandbox/reporting_service.xml
index b9d856cd..864e0650 100644
--- a/bingads/v13/proxies/sandbox/reporting_service.xml
+++ b/bingads/v13/proxies/sandbox/reporting_service.xml
@@ -4438,6 +4438,8 @@
+
+
diff --git a/examples/v13/responsive_ad_recommendation.py b/examples/v13/responsive_ad_recommendation.py
new file mode 100644
index 00000000..aaeda249
--- /dev/null
+++ b/examples/v13/responsive_ad_recommendation.py
@@ -0,0 +1,198 @@
+import base64
+
+from auth_helper import *
+from campaignmanagement_example_helper import *
+
+# You must provide credentials in auth_helper.py.
+
+def main(authorization_data):
+
+ try:
+ # To run this example you'll need to provide a valid Ad Final URL
+ ad_final_url = "https://contoso.com"
+ # Set false to disable cleanup of created entities at the end
+ do_cleanup = True
+
+ final_urls = campaign_service.factory.create('ns3:ArrayOfstring')
+ final_urls.string.append(ad_final_url)
+
+ output_status_message("-----\nCreateResponsiveAdRecommendation:");
+ output_status_message(f"-----\nGetting ad recommendation for URL {ad_final_url} ...");
+ responsive_ad_recommendation_response = campaign_service.CreateResponsiveAdRecommendation(
+ FinalUrls=final_urls
+ )
+ responsive_ad = responsive_ad_recommendation_response.ResponsiveAd
+ image_suggestions = responsive_ad_recommendation_response.ImageSuggestions['AdRecommendationImageSuggestion']
+
+ # Select a few images from the suggested list. This example picks first 5 images
+ selected_images = image_suggestions[:5]
+
+ # Add selected images to your media library
+ save_images(selected_images)
+
+ images = campaign_service.factory.create('ArrayOfAssetLink')
+ images.AssetLink = [obj.AssetLink for obj in selected_images]
+ responsive_ad.Images = images
+
+ responsive_ad.BusinessName = "Contoso"
+ #responsive_ad.CallToAction = 'ShopNow'
+ responsive_ad.CallToActionLanguage = 'English'
+
+ # Create an Audience campaign with one ad group and a responsive ad
+ campaigns = campaign_service.factory.create('ArrayOfCampaign')
+ campaign = set_elements_to_none(campaign_service.factory.create('Campaign'))
+ campaign.BudgetType = 'DailyBudgetStandard'
+ # CampaignType must be set for Audience campaigns
+ campaign.CampaignType = ['Audience']
+ campaign.DailyBudget = 50.00
+ languages = campaign_service.factory.create('ns3:ArrayOfstring')
+ languages.string.append('All')
+ campaign.Languages = languages
+ campaign.Name = "Ad recommendation test " + str(datetime.now())
+ campaign.TimeZone = 'PacificTimeUSCanadaTijuana'
+ campaigns.Campaign.append(campaign)
+
+ output_status_message("-----\nAddCampaigns:")
+ add_campaigns_response = campaign_service.AddCampaigns(
+ AccountId=authorization_data.account_id,
+ Campaigns=campaigns
+ )
+ campaign_ids = {
+ 'long': add_campaigns_response.CampaignIds['long'] if add_campaigns_response.CampaignIds['long'] else None
+ }
+ output_status_message("CampaignIds:")
+ output_array_of_long(campaign_ids)
+ output_status_message("PartialErrors:")
+ output_array_of_batcherror(add_campaigns_response.PartialErrors)
+
+ # Add an ad group within the campaign.
+ ad_groups = campaign_service.factory.create('ArrayOfAdGroup')
+ ad_group = set_elements_to_none(campaign_service.factory.create('AdGroup'))
+ ad_group.Name = "Holiday Sale"
+ ad_group.StartDate = None
+ end_date = campaign_service.factory.create('Date')
+ end_date.Day = 31
+ end_date.Month = 12
+ current_time = gmtime()
+ end_date.Year = current_time.tm_year
+ ad_group.EndDate = end_date
+ cpc_bid = campaign_service.factory.create('Bid')
+ cpc_bid.Amount = 0.09
+ ad_group.CpcBid = cpc_bid
+ # Network cannot be set for ad groups in Audience campaigns
+ ad_group.Network = None
+ ad_groups.AdGroup.append(ad_group)
+
+ output_status_message("-----\nAddAdGroups:")
+ add_ad_groups_response = campaign_service.AddAdGroups(
+ CampaignId=campaign_ids['long'][0],
+ AdGroups=ad_groups,
+ ReturnInheritedBidStrategyTypes=False
+ )
+ ad_group_ids = {
+ 'long': add_ad_groups_response.AdGroupIds['long'] if add_ad_groups_response.AdGroupIds['long'] else None
+ }
+ output_status_message("AdGroupIds:")
+ output_array_of_long(ad_group_ids)
+ output_status_message("PartialErrors:")
+ output_array_of_batcherror(add_ad_groups_response.PartialErrors)
+
+ # Add a responsive ad within the ad group
+ ads = campaign_service.factory.create('ArrayOfAd')
+ ads.Ad.append(responsive_ad)
+
+ output_status_message("-----\nAddAds:")
+ add_ads_response = campaign_service.AddAds(
+ AdGroupId=ad_group_ids['long'][0],
+ Ads=ads
+ )
+ ad_ids = {
+ 'long': add_ads_response.AdIds['long'] if add_ads_response.AdIds['long'] else None
+ }
+ output_status_message("AdIds:")
+ output_array_of_long(ad_ids)
+ output_status_message("PartialErrors:")
+ output_array_of_batcherror(add_ads_response.PartialErrors)
+
+ output_status_message(f"-----\nCreated campaign: + {campaign.Name}")
+
+ if not do_cleanup:
+ return
+ else:
+ # Delete the account's media
+ output_status_message("-----\nDeleteMedia:")
+ media_ids = campaign_service.factory.create('ns3:ArrayOflong')
+ media_ids['long'] = [obj.Asset.Id for obj in responsive_ad.Images.AssetLink]
+ delete_media_response = campaign_service.DeleteMedia(
+ AccountId=authorization_data.account_id,
+ MediaIds=media_ids
+ )
+
+ for media_id in media_ids['long']:
+ output_status_message("Deleted Media Id {0}".format(media_id))
+
+ # Delete the campaign and everything it contains e.g., ad groups and ads
+ output_status_message("-----\nDeleteCampaigns:")
+ campaign_service.DeleteCampaigns(
+ AccountId=authorization_data.account_id,
+ CampaignIds=campaign_ids
+ )
+ output_status_message("Deleted Campaign Id {0}".format(campaign_ids['long'][0]))
+
+
+ except WebFault as ex:
+ output_webfault_errors(ex)
+ except Exception as ex:
+ output_status_message(ex)
+
+def save_images(image_suggestions):
+ medias_to_add = campaign_service.factory.create('ArrayOfMedia')
+ for item in image_suggestions:
+ image = item.Image
+ image_bytes = download_bytes(item.ImageUrl)
+ image_base64 = base64.b64encode(image_bytes).decode('utf-8')
+ image.Data = image_base64
+ medias_to_add.Media.append(image)
+
+ media_ids = campaign_service.AddMedia(
+ AccountId=authorization_data.account_id,
+ Media=medias_to_add
+ )
+
+ for i in range(len(media_ids['long'])):
+ image_suggestions[i].AssetLink.Asset.Id = media_ids['long'][i]
+
+def download_bytes(url):
+ response = requests.get(url, stream=True)
+ response.raise_for_status()
+
+ output_stream = bytearray()
+
+ for chunk in response.iter_content(chunk_size=4096):
+ if chunk:
+ output_stream.extend(chunk)
+
+ return bytes(output_stream)
+
+# Main execution
+if __name__ == '__main__':
+
+ print("Loading the web service client proxies...")
+
+ authorization_data=AuthorizationData(
+ account_id=None,
+ customer_id=None,
+ developer_token=DEVELOPER_TOKEN,
+ authentication=None,
+ )
+
+ campaign_service=ServiceClient(
+ service='CampaignManagementService',
+ version=13,
+ authorization_data=authorization_data,
+ environment=ENVIRONMENT,
+ )
+
+ authenticate(authorization_data)
+
+ main(authorization_data)
diff --git a/setup.py b/setup.py
index d309d24f..7710251c 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
except ImportError:
from distutils.core import setup
-VERSION = '13.0.23.1'
+VERSION = '13.0.24'
with open('README.rst', 'r') as f:
readme = f.read()