Skip to main content

📢 Bing Ads - Ad Management

📖 Overview

Ad management for creating, retrieving, and reporting on various ad types including Expanded Text, Responsive Search, Image, Product, and Dynamic Search ads. No direct campaign-to-ads operation exists in Bing Ads API - ads must be retrieved through ad groups.

Source Files:

  • Controller: external/Integrations/BingAds/Controllers/Ads/AdsController.js
  • Model: external/Integrations/BingAds/Models/Ads/AdsModel.js
  • Routes: external/Integrations/BingAds/Routes/ads.js

External APIs:

  • Campaign Management Service: https://campaign.api.bingads.microsoft.com/Api/Advertiser/CampaignManagement/v13/CampaignManagementService.svc
  • Reporting Service: https://reporting.api.bingads.microsoft.com/Api/Advertiser/Reporting/v13/ReportingService.svc

🏗️ Ad Retrieval Architecture

graph TD
A[Campaign ID] --> B[Get Ad Groups]
B --> C1[Ad Group 1]
B --> C2[Ad Group 2]
C1 --> D1[Get Ads by Ad Group]
C2 --> D2[Get Ads by Ad Group]
D1 --> E1[Ads Array]
D2 --> E2[Ads Array]

style A fill:#4CAF50
style B fill:#2196F3
style C1 fill:#FF9800
style C2 fill:#FF9800
style D1 fill:#9C27B0
style D2 fill:#9C27B0
style E1 fill:#E91E63
style E2 fill:#E91E63

Important: Bing Ads API has no GetAdsByCampaignId operation. Available operations are:

  • GetAdsByAdGroupId - Get ads by ad group
  • GetAdsByIds - Get specific ads by ID
  • GetAdsByEditorialStatus - Get ads by editorial status

Workaround: To get ads by campaign, the code:

  1. Gets ad groups for campaign
  2. Loops through ad groups
  3. Gets ads for each ad group
  4. Aggregates results

🔄 Data Flow

sequenceDiagram
participant Client as API Client
participant DC as DashClicks API
participant MW as Token Middleware
participant Model as AdsModel
participant SOAP as Bing SOAP API

Client->>DC: GET /ads?campaignID={id}
DC->>MW: Check token expiration
MW->>DC: Valid access token
DC->>Model: getAdsByCampaignID()
Model->>SOAP: POST GetAdGroupsByCampaignId
SOAP-->>Model: Ad groups list
loop For each ad group
Model->>SOAP: POST GetAdsByAdGroupId
SOAP-->>Model: Ads for ad group
end
Model->>Model: Aggregate campaign + adgroup + ads
Model-->>DC: Nested structure
DC-->>Client: JSON response

🔧 Ad Functions

List Ads by Campaign

GET /ads?campaignID={id}

Purpose: Retrieve all ads for a specific campaign (via ad groups)

Controller: AdsController.index()getAdsByCampaignID()

Model Method: AdsModel.getAdsByCampaignID(customerAccountID, customerID, campaignID)

SOAP Operations: GetAdGroupsByCampaignId + multiple GetAdsByAdGroupId calls

Request:

GET /v1/integrations/bing/ads/ads?campaignID=987654321&customerAccountID=111222333&customerID=123456
Authorization: Bearer {jwt_token}

Query Parameters:

ParameterTypeRequiredDescription
campaignIDIntegerCampaign ID to filter ads
customerAccountIDIntegerAccount ID
customerIDIntegerCustomer ID (optional)

Validation:

  • If campaignID missing, returns error: "You can get ads by campaign ID and adgroup ID"

Business Logic Flow:

  1. Get Ad Groups: Call getAdgroupFilteredByCampaignID() to get campaign's ad groups
  2. Check Fault: If SOAP fault returned, pass through error
  3. Build Promises: Create promise array for parallel ad fetching
  4. Loop Ad Groups: For each ad group, call getAdsByAdGroupId()
  5. Aggregate: Combine campaign + ad group + ads into nested structure
  6. Return: Single object if one ad group, array if multiple

SOAP Request XML (GetAdsByAdGroupId):

<s:Envelope xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header xmlns="https://bingads.microsoft.com/CampaignManagement/v13">
<Action mustUnderstand="1">GetAdsByAdGroupId</Action>
<AuthenticationToken i:nil="false">{ACCESS_TOKEN}</AuthenticationToken>
<CustomerAccountId i:nil="false">111222333</CustomerAccountId>
<CustomerId i:nil="false">123456</CustomerId>
<DeveloperToken i:nil="false">{DEVELOPER_TOKEN}</DeveloperToken>
</s:Header>
<s:Body>
<GetAdsByAdGroupIdRequest xmlns="https://bingads.microsoft.com/CampaignManagement/v13">
<AdGroupId>111222333</AdGroupId>
<AdTypes i:nil="false">
<AdType>ExpandedText</AdType>
<AdType>Text</AdType>
<AdType>Image</AdType>
<AdType>Product</AdType>
<AdType>AppInstall</AdType>
<AdType>DynamicSearch</AdType>
<AdType>ResponsiveAd</AdType>
<AdType>ResponsiveSearch</AdType>
</AdTypes>
</GetAdsByAdGroupIdRequest>
</s:Body>
</s:Envelope>

Default Ad Types (if no filter provided):

  • ExpandedText - Standard text ads with extended headlines
  • Text - Legacy text ads
  • Image - Image/banner ads
  • Product - Shopping product ads
  • AppInstall - Mobile app install ads
  • DynamicSearch - Dynamic search ads (auto-generated)
  • ResponsiveAd - Responsive display ads
  • ResponsiveSearch - Responsive search ads

JSON Response (Single Ad Group):

{
"success": true,
"message": "SUCCESS",
"data": {
"campaign": {
"CampaignId": 987654321,
"Name": "Search Campaign",
"Status": "Active"
},
"adgroup": {
"Id": 111222333,
"Name": "Ad Group 1",
"Status": "Active"
},
"ad": [
{
"@i:type": "ResponsiveSearchAd",
"Id": 444555666,
"AdGroupId": 111222333,
"Status": "Active",
"FinalUrls": {
"string": ["https://example.com/landing"]
},
"Headlines": {
"AssetLink": [
{
"Asset": {
"@i:type": "TextAsset",
"Text": "Buy Quality Products"
},
"PinnedField": "Headline1"
},
{
"Asset": {
"@i:type": "TextAsset",
"Text": "Free Shipping Today"
}
}
]
},
"Descriptions": {
"AssetLink": [
{
"Asset": {
"@i:type": "TextAsset",
"Text": "Shop our wide selection of quality products with free shipping."
}
}
]
},
"Path1": "products",
"Path2": "deals"
}
]
}
}

JSON Response (Multiple Ad Groups):

{
"success": true,
"message": "SUCCESS",
"data": [
{
"campaign": { ... },
"adgroup": { ... },
"ad": [ ... ]
},
{
"campaign": { ... },
"adgroup": { ... },
"ad": [ ... ]
}
]
}

Side Effects:

  • ⚠️ Multiple API Calls: One call per ad group (performance consideration)
  • ⚠️ Async Promises: Uses Promise.all() for parallel execution
  • None (read-only operation)

List Ads by Ad Group

GET /ads?adGroupID={id}

Purpose: Retrieve ads for a specific ad group (direct operation)

Controller: AdsController.index()getAdsByAdGroupId()

Model Method: AdsModel.getAdsByAdGroupId(customerAccountID, customerID, adGroupID, adType)

SOAP Operation: GetAdsByAdGroupId

Request:

GET /v1/integrations/bing/ads/ads?adGroupID=111222333&customerAccountID=111222333&customerID=123456
Authorization: Bearer {jwt_token}

Query Parameters:

ParameterTypeRequiredDescription
adGroupIDIntegerAd group ID to filter ads
customerAccountIDIntegerAccount ID
customerIDIntegerCustomer ID (optional)
adTypeStringFilter by specific ad type

JSON Response:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"@i:type": "ExpandedTextAd",
"Id": 777888999,
"AdGroupId": 111222333,
"Status": "Active",
"EditorialStatus": "Active",
"FinalUrls": {
"string": ["https://example.com"]
},
"TitlePart1": "Quality Products",
"TitlePart2": "Shop Now",
"TitlePart3": "Save Big",
"Text": "Browse our wide selection of quality products. Free shipping on orders over $50.",
"TextPart2": "Limited time offer!",
"Path1": "products",
"Path2": "sale",
"DisplayUrl": "example.com/products/sale"
},
{
"@i:type": "ResponsiveSearchAd",
"Id": 222333444,
"AdGroupId": 111222333,
"Status": "Active",
"Headlines": {
"AssetLink": [
{ "Asset": { "Text": "Best Deals" } },
{ "Asset": { "Text": "Shop Today" } },
{ "Asset": { "Text": "Free Shipping" } }
]
},
"Descriptions": {
"AssetLink": [
{ "Asset": { "Text": "Get amazing deals on quality products." } },
{ "Asset": { "Text": "Limited time offer. Shop now!" } }
]
}
}
]
}

Ad Type-Specific Fields:

Expanded Text Ad (ExpandedTextAd):

  • TitlePart1, TitlePart2, TitlePart3 - Three headline parts (30 chars each)
  • Text - Description line 1 (90 chars)
  • TextPart2 - Description line 2 (90 chars)
  • Path1, Path2 - Display URL paths (15 chars each)

Responsive Search Ad (ResponsiveSearchAd):

  • Headlines.AssetLink - Array of headline assets (up to 15, min 3)
  • Descriptions.AssetLink - Array of description assets (up to 4, min 2)
  • PinnedField - Optional pinning (Headline1, Headline2, Description1, Description2)

Dynamic Search Ad (DynamicSearchAd):

  • Text - Description text
  • Path1, Path2 - Display URL paths
  • No headlines (auto-generated from landing page)

Get Ad by ID

GET /ads/{adID}

Purpose: Retrieve specific ad by ID

Controller: AdsController.show()

Model Method: AdsModel.getAdbyAdId(customerAccountID, customerID, adGroupID, adID, adType)

SOAP Operation: GetAdsByIds

Request:

GET /v1/integrations/bing/ads/ads/444555666?adGroupID=111222333&customerAccountID=111222333&customerID=123456&adType=ResponsiveSearch
Authorization: Bearer {jwt_token}

Query Parameters:

ParameterTypeRequiredDescription
adIDIntegerSpecific ad ID
adGroupIDIntegerParent ad group ID
customerAccountIDIntegerAccount ID
customerIDIntegerCustomer ID (optional)
adTypeStringFilter by ad type (default: ExpandedText)

Validation:

  • If required params missing, returns error: "Please provide customerAccountID, adGroupID and adID. optional you can provide customerID and adType as well as"

SOAP Request XML:

<s:Envelope xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header xmlns="https://bingads.microsoft.com/CampaignManagement/v13">
<Action mustUnderstand="1">GetAdsByIds</Action>
<AuthenticationToken i:nil="false">{ACCESS_TOKEN}</AuthenticationToken>
<CustomerAccountId i:nil="false">111222333</CustomerAccountId>
<CustomerId i:nil="false">123456</CustomerId>
<DeveloperToken i:nil="false">{DEVELOPER_TOKEN}</DeveloperToken>
</s:Header>
<s:Body>
<GetAdsByIdsRequest xmlns="https://bingads.microsoft.com/CampaignManagement/v13">
<AdGroupId>111222333</AdGroupId>
<AdIds i:nil="false" xmlns:a1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a1:long>444555666</a1:long>
</AdIds>
<AdTypes i:nil="false">
<AdType>ResponsiveSearch</AdType>
</AdTypes>
</GetAdsByIdsRequest>
</s:Body>
</s:Envelope>

Helper Method: generateAdIdsXml(adID)

  • Splits comma-separated IDs
  • Generates XML: <a1:long>{id}</a1:long> for each ID

JSON Response:

{
"success": true,
"message": "SUCCESS",
"data": {
"@i:type": "ResponsiveSearchAd",
"Id": 444555666,
"AdGroupId": 111222333,
"Status": "Active",
"FinalUrls": {
"string": ["https://example.com/products"]
},
"Headlines": { ... },
"Descriptions": { ... }
}
}

Get Ad Performance Metrics

GET /ads/metrics

Purpose: Generate asynchronous performance report for ads

Controller: AdsController.metrics()

Model Method: AdsModel.getAdReport(...)

SOAP Operation: SubmitGenerateReport

Service: Reporting Service v13

Request:

GET /v1/integrations/bing/ads/ads/metrics?customerAccountID=111222333&customerID=123456&campaignID=987654321&adGroupID=111222333&adType=ResponsiveSearch&aggregation=Daily&predefinedTime=Last7Days
Authorization: Bearer {jwt_token}

Query Parameters:

ParameterTypeRequiredDescriptionDefault
customerAccountIDIntegerAccount ID-
customerIDIntegerCustomer ID-
campaignIDIntegerSpecific campaign IDAll campaigns
campaignTypeStringCampaign type filterSearch
adGroupIDIntegerSpecific ad group IDAll ad groups
adTypeStringAd type filterExpandedText
aggregationStringAggregation levelMonthly
predefinedTimeStringPredefined time periodLast30Days
timeZoneStringReport timezoneEasternTimeUSCanada
fromDateStringCustom start date (YYYY-MM-DD)-
endDateStringCustom end date (YYYY-MM-DD)-

SOAP Request XML:

<s:Envelope xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header xmlns="https://bingads.microsoft.com/Reporting/v13">
<Action mustUnderstand="1">SubmitGenerateReport</Action>
<AuthenticationToken i:nil="false">{ACCESS_TOKEN}</AuthenticationToken>
<CustomerAccountId i:nil="true">111222333</CustomerAccountId>
<CustomerId i:nil="false">123456</CustomerId>
<DeveloperToken i:nil="false">{DEVELOPER_TOKEN}</DeveloperToken>
</s:Header>
<s:Body>
<SubmitGenerateReportRequest xmlns="https://bingads.microsoft.com/Reporting/v13">
<ReportRequest i:nil="false" i:type="AdPerformanceReportRequest">
<ExcludeColumnHeaders>false</ExcludeColumnHeaders>
<ExcludeReportFooter>false</ExcludeReportFooter>
<ExcludeReportHeader>false</ExcludeReportHeader>
<Format>Csv</Format>
<Language>English</Language>
<ReportName>AdPerformanceReportRequest</ReportName>
<ReturnOnlyCompleteData>false</ReturnOnlyCompleteData>
<Aggregation>Daily</Aggregation>
<Columns>
<AdPerformanceReportColumn>AccountName</AdPerformanceReportColumn>
<AdPerformanceReportColumn>AdId</AdPerformanceReportColumn>
<AdPerformanceReportColumn>AdTitle</AdPerformanceReportColumn>
<AdPerformanceReportColumn>AdDescription</AdPerformanceReportColumn>
<AdPerformanceReportColumn>Impressions</AdPerformanceReportColumn>
<AdPerformanceReportColumn>Clicks</AdPerformanceReportColumn>
<!-- 65+ more columns available -->
</Columns>
<Filter i:nil="false">
<AdType i:nil="false">ResponsiveSearch</AdType>
<CampaignType i:nil="false">Search</CampaignType>
</Filter>
<Scope i:nil="false">
<AccountIds i:nil="false" xmlns:a1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a1:long>111222333</a1:long>
</AccountIds>
<!-- Optional: Specific campaign and ad group -->
<AdGroups i:nil="false">
<AdGroupReportScope>
<AccountId>111222333</AccountId>
<CampaignId>987654321</CampaignId>
<AdGroupId>111222333</AdGroupId>
</AdGroupReportScope>
</AdGroups>
<Campaigns i:nil="false">
<CampaignReportScope>
<AccountId>111222333</AccountId>
<CampaignId>987654321</CampaignId>
</CampaignReportScope>
</Campaigns>
</Scope>
<Time>
<PredefinedTime i:nil="false">Last7Days</PredefinedTime>
<ReportTimeZone>EasternTimeUSCanada</ReportTimeZone>
</Time>
</ReportRequest>
</SubmitGenerateReportRequest>
</s:Body>
</s:Envelope>

Available Report Columns (71 total):

ColumnDescription
AccountName, AccountNumber, AccountIdAccount identifiers
CampaignName, CampaignId, CampaignStatusCampaign info
AdGroupName, AdGroupId, AdGroupStatusAd group info
AdId, AdTitle, AdDescription, AdTypeAd creative info
DestinationUrl, DisplayUrlLanding page and display URL
Impressions, Clicks, CtrCore metrics
Spend, AverageCpc, AveragePositionCost metrics
Conversions, ConversionRate, CostPerConversionConversion metrics
Revenue, ReturnOnAdSpend, RevenuePerConversionRevenue metrics
QualityScore, ExpectedCtr, AdRelevance, LandingPageExperienceQuality metrics
DeviceType, Language, NetworkTargeting dimensions
AllConversions, AllRevenue, ViewThroughConversionsAdvanced metrics

Full columns list: See getAdsColumn() method in source code (71 columns total)

JSON Response:

{
"success": true,
"message": "SUCCESS",
"data": {
"ReportRequestId": "987654321012345"
}
}

Important: This is an asynchronous operation:

  1. Returns ReportRequestId immediately
  2. Report generation happens in background
  3. Must poll PollGenerateReport to check status
  4. When Success, download CSV via ReportRequestId

📊 Ad Types

TypeDescriptionUse Case
ExpandedTextStandard text ads with 3 headlines + 2 descriptionsMost common search ads
ResponsiveSearchDynamic ads with multiple headlines/descriptionsGoogle-style responsive ads
TextLegacy text ads (deprecated)Old campaigns
ImageBanner/display image adsVisual campaigns
ProductShopping product listing adsE-commerce
AppInstallMobile app installation adsApp promotion
DynamicSearchAuto-generated ads from websiteLarge inventories
ResponsiveAdResponsive display adsDisplay network

📏 Character Limits

Expanded Text Ads

FieldCharacter Limit
TitlePart130 characters
TitlePart230 characters
TitlePart330 characters
Text (Description 1)90 characters
TextPart2 (Description 2)90 characters
Path115 characters
Path215 characters

Responsive Search Ads

FieldCount LimitCharacter Limit
Headlines3-15 assets30 chars each
Descriptions2-4 assets90 chars each
Path1115 characters
Path2115 characters

🎯 Editorial Status Values

StatusDescription
ActiveAd approved and serving
ActiveLimitedAd serving with limitations
DisapprovedAd rejected by editorial review
InactiveAd not serving (paused or deleted)

🔄 Helper Methods

generateAdIdsXml(adID)

Converts comma-separated ad IDs to SOAP XML format.

Input: "444555666,777888999"

Output:

<a1:long>444555666</a1:long>
<a2:long>777888999</a2:long>

getAdsColumn()

Returns array of 71 available report column names for ad performance reports.

⚠️ Important Notes

  • 🚫 No Direct Campaign Query: Cannot get ads directly by campaign ID
  • 🔄 Multiple API Calls: Campaign query requires looping through ad groups
  • 📊 Async Reporting: Metrics endpoint returns request ID, not actual data
  • 🕒 Report Polling: Must implement polling to check report completion
  • 📁 CSV Format: Reports returned as CSV files (Format: Csv)
  • 🎯 Ad Group Required: Ads always belong to ad groups
  • 🔢 Default Ad Type: If not specified, defaults to ExpandedText for reporting
  • 📊 71 Metrics Available: Full list in getAdsColumn()
  • 🌍 Timezone: Affects date range interpretation
  • 🔐 Ad Type Filter: Can filter both queries and reports by ad type
  • 🔄 Token Auto-Refresh: Middleware ensures valid tokens
  • 🏗️ SOAP Protocol: XML requests/responses (parsed to JSON)
  • 📋 Campaign Type Filter: Can filter reports by Search, Shopping, or DynamicSearchAds
  • 🎨 Responsive Ads: Support dynamic asset combinations
  • Promise.all(): Uses parallel execution for multiple ad group queries
💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:30 AM