📁 Bing Ads - Ad Group Management
📖 Overview
Ad Groups organize ads within campaigns and define targeting settings. The Campaign Management Service provides SOAP operations for retrieving ad group information and generating performance reports. Ad groups can be filtered by campaign or retrieved for entire accounts.
Source Files:
- Controller:
external/Integrations/BingAds/Controllers/AdGroups/AdGroupController.js - Model:
external/Integrations/BingAds/Models/AdGroups/AdGroupModel.js - Routes:
external/Integrations/BingAds/Routes/adgroups.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 Group Hierarchy
graph TD
A[Account] --> C1[Campaign 1]
A --> C2[Campaign 2]
C1 --> AG1[Ad Group 1]
C1 --> AG2[Ad Group 2]
C2 --> AG3[Ad Group 3]
AG1 --> AD1[Ad 1]
AG1 --> AD2[Ad 2]
AG2 --> AD3[Ad 3]
AG3 --> AD4[Ad 4]
style A fill:#4CAF50
style C1 fill:#2196F3
style C2 fill:#2196F3
style AG1 fill:#FF9800
style AG2 fill:#FF9800
style AG3 fill:#FF9800
style AD1 fill:#9C27B0
style AD2 fill:#9C27B0
style AD3 fill:#9C27B0
style AD4 fill:#9C27B0
🔄 Data Flow
sequenceDiagram
participant Client as API Client
participant DC as DashClicks API
participant MW as Token Middleware
participant Model as AdGroupModel
participant SOAP as Bing SOAP API
Client->>DC: GET /adgroups
DC->>MW: Check token expiration
MW->>DC: Valid access token
DC->>Model: getAdgroupsUnfiltered() or getAdgroupFilteredByCampaignID()
Model->>SOAP: POST GetCampaignsByAccountId (if unfiltered)
SOAP-->>Model: Campaign list
loop For each campaign
Model->>SOAP: POST GetAdGroupsByCampaignId
SOAP-->>Model: Ad groups for campaign
end
Model-->>DC: Campaign + Ad Group pairs
DC-->>Client: JSON response
🔧 Ad Group Functions
List Ad Groups (Unfiltered)
GET /adgroups
Purpose: Retrieve all ad groups across all campaigns for an account
Controller: AdGroupController.index() → getAdgroupsUnfiltered()
Model Method: AdGroupModel.getAdgroupsUnfiltered(customerAccountID, customerID, campaignType)
SOAP Operations: GetCampaignsByAccountId + GetAdGroupsByCampaignId (multiple calls)
Request:
GET /v1/integrations/bing/ads/adgroups?customerAccountID=111222333&customerID=123456&campaignType=Search
Authorization: Bearer {jwt_token}
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
customerAccountID | Integer | ✅ | Account ID from /accessible-user-accounts |
customerID | Integer | ⚪ | Customer ID (optional but recommended) |
campaignType | String | ⚪ | Filter: Search, Shopping, DynamicSearchAds |
Validation:
- If
customerAccountIDmissing, returns error: "Please provide customerAccountID optionaly you can provide customerID and campaignType as well as"
Business Logic Flow:
- Get Campaigns: Call
getCampaignsByAccountId()to retrieve all campaigns - Check Array: Determine if single campaign or multiple campaigns returned
- Loop Through Campaigns: For each campaign, call
getAdGroupByCampaignID() - Aggregate Results: Build array of campaign + ad group pairs
- Return: Nested structure with campaigns and their ad groups
SOAP Request XML (GetAdGroupsByCampaignId):
<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">GetAdGroupsByCampaignId</Action>
<AuthenticationToken i:nil="false">{ACCESS_TOKEN}</AuthenticationToken>
<CustomerAccountId i:nil="false">111222333</CustomerAccountId>
<CustomerId i:nil="true">123456</CustomerId>
<DeveloperToken i:nil="false">{DEVELOPER_TOKEN}</DeveloperToken>
</s:Header>
<s:Body>
<GetAdGroupsByCampaignIdRequest xmlns="https://bingads.microsoft.com/CampaignManagement/v13">
<CampaignId>987654321</CampaignId>
</GetAdGroupsByCampaignIdRequest>
</s:Body>
</s:Envelope>
JSON Response (Multiple Campaigns):
{
"success": true,
"message": "SUCCESS",
"data": [
{
"campaign": {
"Id": 987654321,
"Name": "Search Campaign",
"Status": "Active",
"BudgetType": "DailyBudgetStandard",
"DailyBudget": 50.00
},
"adgroup": [
{
"Id": 111222333,
"Name": "Ad Group 1",
"Status": "Active",
"CpcBid": {
"Amount": 1.50
},
"Language": "English",
"Network": "OwnedAndOperatedAndSyndicatedSearch"
},
{
"Id": 444555666,
"Name": "Ad Group 2",
"Status": "Paused",
"CpcBid": {
"Amount": 2.00
},
"Language": "English"
}
]
},
{
"campaign": {
"Id": 555666777,
"Name": "Shopping Campaign",
"Status": "Active"
},
"adgroup": [
{
"Id": 777888999,
"Name": "Product Group",
"Status": "Active"
}
]
}
]
}
JSON Response (Single Campaign):
{
"success": true,
"message": "SUCCESS",
"data": {
"campaign": {
"Id": 987654321,
"Name": "Search Campaign",
"Status": "Active"
},
"adgroup": [
{
"Id": 111222333,
"Name": "Ad Group 1",
"Status": "Active"
}
]
}
}
Key Response Fields:
| Field | Type | Description |
|---|---|---|
campaign | Object | Campaign details |
adgroup | Array/Object | Ad groups for the campaign |
Id | Integer | Ad group identifier |
Name | String | Ad group name |
Status | String | Active, Paused, Deleted |
CpcBid.Amount | Decimal | Cost-per-click bid amount |
Language | String | Target language |
Network | String | Distribution network |
Side Effects:
- ⚠️ Multiple API Calls: One call per campaign (can be slow for many campaigns)
- None (read-only operation)
List Ad Groups (Filtered by Campaign)
GET /adgroups?campaignID={id}
Purpose: Retrieve ad groups for a specific campaign
Controller: AdGroupController.index() → getAdgroupFilteredByCampaignID()
Model Method: AdGroupModel.getAdgroupFilteredByCampaignID(customerAccountID, customerID, campaignID)
SOAP Operations: GetAdGroupsByCampaignId + GetCampaignsByIds
Request:
GET /v1/integrations/bing/ads/adgroups?campaignID=987654321&customerAccountID=111222333&customerID=123456
Authorization: Bearer {jwt_token}
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
campaignID | Integer | ✅ | Specific campaign ID to filter |
customerAccountID | Integer | ✅ | Account ID |
customerID | Integer | ✅ | Customer ID |
Business Logic Flow:
- Get Ad Groups: Call
getAdGroupByCampaignID()for specific campaign - Get Campaign Details: Call
getCampaignsByIds()to get campaign info - Combine: Return both campaign and ad groups together
- Error Check: If SOAP fault, return fault object
JSON Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"campaign": {
"CampaignId": 987654321,
"Name": "Search Campaign Name",
"Status": "Active",
"BudgetType": "DailyBudgetStandard",
"DailyBudget": 50.00
},
"adgroup": [
{
"Id": 111222333,
"CampaignId": 987654321,
"Name": "Ad Group 1",
"Status": "Active",
"StartDate": {
"Day": 1,
"Month": 10,
"Year": 2025
},
"EndDate": null,
"CpcBid": {
"Amount": 1.50
},
"Language": "English",
"Network": "OwnedAndOperatedAndSyndicatedSearch",
"BiddingScheme": {
"@i:type": "ManualCpcBiddingScheme"
},
"AdDistribution": "Search",
"AdRotation": {
"Type": "RotateAdsEvenly"
}
}
]
}
}
Ad Group Object Fields:
| Field | Type | Description |
|---|---|---|
Id | Integer | Ad group ID |
CampaignId | Integer | Parent campaign ID |
Name | String | Ad group name |
Status | String | Active, Paused, Deleted |
StartDate | Object | Start date (Day/Month/Year) |
EndDate | Object/Null | End date or null for ongoing |
CpcBid.Amount | Decimal | Manual CPC bid amount |
Language | String | Language targeting |
Network | String | Distribution network |
BiddingScheme | Object | Bidding strategy type |
AdDistribution | String | Search, Content, All |
AdRotation.Type | String | Ad rotation strategy |
Side Effects:
- None (read-only operation)
Get Ad Group by ID
GET /adgroups/{adGroupID}
Purpose: Retrieve specific ad group by ID
Controller: AdGroupController.show()
Model Method: AdGroupModel.getAdGroupByID(customerAccountID, customerID, campaignID, adGroupID)
SOAP Operation: GetAdGroupsByIds
Request:
GET /v1/integrations/bing/ads/adgroups/111222333?customerAccountID=111222333&customerID=123456&campaignID=987654321
Authorization: Bearer {jwt_token}
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
customerAccountID | Integer | ✅ | Account ID |
customerID | Integer | ✅ | Customer ID |
campaignID | Integer | ✅ | Campaign ID (ad group parent) |
adGroupID | Integer | ✅ | Specific ad group ID |
Validation:
- If any required param missing, returns error: "Please provide customerAccountID,customerID and adGroupID."
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">GetAdGroupsByIds</Action>
<AuthenticationToken i:nil="false">{ACCESS_TOKEN}</AuthenticationToken>
<CustomerAccountId i:nil="false">111222333</CustomerAccountId>
<CustomerId i:nil="true">123456</CustomerId>
<DeveloperToken i:nil="false">{DEVELOPER_TOKEN}</DeveloperToken>
</s:Header>
<s:Body>
<GetAdGroupsByIdsRequest xmlns="https://bingads.microsoft.com/CampaignManagement/v13">
<CampaignId>987654321</CampaignId>
<AdGroupIds i:nil="false" xmlns:a1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a1:long>111222333</a1:long>
</AdGroupIds>
</GetAdGroupsByIdsRequest>
</s:Body>
</s:Envelope>
JSON Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"AdGroups": {
"Id": 111222333,
"CampaignId": 987654321,
"Name": "Ad Group 1",
"Status": "Active",
"CpcBid": {
"Amount": 1.50
},
"Language": "English",
"Network": "OwnedAndOperatedAndSyndicatedSearch"
}
}
}
Get Ad Group Performance Metrics
GET /adgroups/metrics
Purpose: Generate asynchronous performance report for ad groups
Controller: AdGroupController.metrics()
Model Method: AdGroupModel.adGroupReport(...)
SOAP Operation: SubmitGenerateReport
Service: Reporting Service v13
Request:
GET /v1/integrations/bing/ads/adgroups/metrics?customerAccountID=111222333&customerID=123456&campaignID=987654321&adGroupID=111222333&aggregation=Daily&predefinedTime=Last7Days
Authorization: Bearer {jwt_token}
Query Parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
customerAccountID | Integer | ✅ | Account ID | - |
customerID | Integer | ⚪ | Customer ID | - |
campaignID | Integer | ⚪ | Specific campaign ID | All campaigns |
campaignType | String | ⚪ | Campaign type filter | All types |
adGroupID | Integer | ⚪ | Specific ad group ID | All ad groups |
aggregation | String | ⚪ | Aggregation level | Monthly |
predefinedTime | String | ⚪ | Predefined time period | Last30Days |
timeZone | String | ⚪ | Report timezone | EasternTimeUSCanada |
fromDate | String | ⚪ | Custom start date (YYYY-MM-DD) | - |
endDate | String | ⚪ | Custom 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="AdGroupPerformanceReportRequest">
<ExcludeColumnHeaders>false</ExcludeColumnHeaders>
<ExcludeReportFooter>false</ExcludeReportFooter>
<ExcludeReportHeader>false</ExcludeReportHeader>
<Format>Csv</Format>
<Language>English</Language>
<ReportName>AdGroupPerformanceReportRequest</ReportName>
<ReturnOnlyCompleteData>false</ReturnOnlyCompleteData>
<Aggregation>Daily</Aggregation>
<Columns>
<AdGroupPerformanceReportColumn>AccountName</AdGroupPerformanceReportColumn>
<AdGroupPerformanceReportColumn>AdGroupId</AdGroupPerformanceReportColumn>
<AdGroupPerformanceReportColumn>AdGroupName</AdGroupPerformanceReportColumn>
<AdGroupPerformanceReportColumn>Impressions</AdGroupPerformanceReportColumn>
<AdGroupPerformanceReportColumn>Clicks</AdGroupPerformanceReportColumn>
<!-- 56+ more columns available -->
</Columns>
<Filter i:nil="false">
<CampaignType i:nil="false">Search</CampaignType>
</Filter>
<Scope>
<AccountIds 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 (56 total):
| Column | Description |
|---|---|
AccountName, AccountNumber, AccountId | Account identifiers |
CampaignName, CampaignId, CampaignStatus | Campaign info |
AdGroupName, AdGroupId, Status | Ad group info |
Impressions, Clicks, Ctr | Core metrics |
Spend, AverageCpc, AveragePosition | Cost metrics |
Conversions, ConversionRate, CostPerConversion | Conversion metrics |
Revenue, ReturnOnAdSpend, RevenuePerConversion | Revenue metrics |
QualityScore, ExpectedCtr, AdRelevance, LandingPageExperience | Quality metrics |
DeviceType, Language, Network | Targeting dimensions |
AllConversions, AllRevenue, ViewThroughConversions | Advanced metrics |
Full columns list: See getAdgroupColumns() method in source code (56 columns total)
JSON Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"ReportRequestId": "987654321012345"
}
}
Important: This is an asynchronous operation:
- Returns
ReportRequestIdimmediately - Report generation happens in background
- Must poll
PollGenerateReportto check status - When
Success, download CSV viaReportRequestId
📊 Ad Group Status Values
| Status | Description |
|---|---|
Active | Ad group is active and serving ads |
Paused | Ad group is manually paused |
Deleted | Ad group is deleted (soft delete) |
🎯 Ad Distribution Options
| Distribution | Description |
|---|---|
Search | Bing search results only |
Content | Syndicated partner sites only |
All | Both search and content network |
🌐 Network Options
| Network | Description |
|---|---|
OwnedAndOperatedAndSyndicatedSearch | Bing, AOL, Yahoo + syndicated partners |
OwnedAndOperatedOnly | Bing, AOL, Yahoo only |
SyndicatedSearchOnly | Syndicated partners only |
🔄 Bidding Scheme Types
| Type | Description |
|---|---|
ManualCpcBiddingScheme | Manual cost-per-click bidding |
EnhancedCpcBiddingScheme | Enhanced CPC (automatic adjustments) |
MaxClicksBiddingScheme | Maximize clicks within budget |
MaxConversionsBiddingScheme | Maximize conversions |
TargetCpaBiddingScheme | Target cost per acquisition |
📝 Helper Method
getAdgroupColumns()
Returns array of 56 available report column names for ad group performance reports.
⚠️ Important Notes
- 🔄 Multiple API Calls: Unfiltered endpoint makes one call per campaign (performance consideration)
- 📊 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)
- 🎯 Campaign Required: Ad groups always belong to campaigns
- 🔢 Campaign Filter: Use
campaignIDquery param for faster, targeted results - 📊 56 Metrics Available: Full list in
getAdgroupColumns() - 🌍 Timezone: Affects date range interpretation
- 🔐 Three IDs Required: customerAccountID, customerID, and campaignID for specific ad group retrieval
- 🔄 Token Auto-Refresh: Middleware ensures valid tokens
- 🏗️ SOAP Protocol: XML requests/responses (parsed to JSON)
- 📋 Campaign Type Filter: Can filter reports by
Search,Shopping, orDynamicSearchAds
🔗 Related Documentation
- Authentication: OAuth 2.0 with Auto-Refresh
- Accounts: Account Hierarchy and Selection
- Campaigns: Campaign Management
- Ads: Ad Management
- Keywords: Keyword Management
- Reports: Async Report Generation
- Microsoft Docs: Campaign Management Service
- Ad Groups API: GetAdGroupsByCampaignId