Skip to main content

🔑 Bing Ads - Keyword Management

📖 Overview

Keyword management for targeting search queries with various match types, bid strategies, and quality score optimization. Like ads, there is no direct campaign-to-keywords operation in Bing Ads API - keywords must be retrieved through ad groups.

Source Files:

  • Controller: external/Integrations/BingAds/Controllers/Keywords/KeywordsController.js
  • Model: external/Integrations/BingAds/Models/Keywords/KeywordsModel.js
  • Routes: external/Integrations/BingAds/Routes/keywords.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

🏗️ Keyword Retrieval Architecture

graph TD
A[Campaign ID] --> B[Get Ad Groups]
B --> C1[Ad Group 1]
B --> C2[Ad Group 2]
C1 --> D1[Get Keywords by Ad Group]
C2 --> D2[Get Keywords by Ad Group]
D1 --> E1[Keywords Array]
D2 --> E2[Keywords 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 GetKeywordsByCampaignId operation. Available operations are:

  • GetKeywordsByAdGroupId - Get keywords by ad group
  • GetKeywordsByIds - Get specific keywords by ID
  • GetKeywordsByEditorialStatus - Get keywords by editorial status

Workaround: To get keywords by campaign, the code:

  1. Gets ad groups for campaign
  2. Loops through ad groups
  3. Gets keywords 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 KeywordsModel
participant SOAP as Bing SOAP API

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

🔧 Keyword Functions

List Keywords by Campaign

GET /keywords?campaignID={id}

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

Controller: KeyWordsController.index()getKeywordsByCampaignID()

Model Method: KeywordsModel.getKeywordsByCampaignID(customerAccountID, customerID, campaignID)

SOAP Operations: GetAdGroupsByCampaignId + multiple GetKeywordsByAdGroupId calls

Request:

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

Query Parameters:

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

Validation:

  • If campaignID missing, returns error: "You can get keywords 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 keyword fetching
  4. Loop Ad Groups: For each ad group, call getKeywordsByAdGroupId()
  5. Aggregate: Combine campaign + ad group + keywords into nested structure
  6. Return: Single object if one ad group, array if multiple

SOAP Request XML (GetKeywordsByAdGroupId):

<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">GetKeywordsByAdGroupId</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>
<GetKeywordsByAdGroupIdRequest xmlns="https://bingads.microsoft.com/CampaignManagement/v13">
<AdGroupId>111222333</AdGroupId>
</GetKeywordsByAdGroupIdRequest>
</s:Body>
</s:Envelope>

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"
},
"keyword": [
{
"Id": 444555666,
"AdGroupId": 111222333,
"Text": "quality products",
"MatchType": "Phrase",
"Bid": {
"Amount": 2.50
},
"Status": "Active",
"EditorialStatus": "Active",
"DestinationUrl": "https://example.com/products",
"Param1": null,
"Param2": null,
"Param3": null
},
{
"Id": 777888999,
"AdGroupId": 111222333,
"Text": "buy products online",
"MatchType": "Broad",
"Bid": {
"Amount": 1.75
},
"Status": "Active",
"EditorialStatus": "Active"
}
]
}
}

JSON Response (Multiple Ad Groups):

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

Key Response Fields:

FieldTypeDescription
campaignObjectCampaign details
adgroupObjectAd group details
keywordArrayKeywords for the ad group
IdIntegerKeyword identifier
TextStringKeyword text/phrase
MatchTypeStringExact, Phrase, Broad
Bid.AmountDecimalMaximum CPC bid amount
StatusStringActive, Paused, Deleted
EditorialStatusStringEditorial review status
DestinationUrlStringLanding page URL

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 Keywords by Ad Group

GET /keywords?adGroupID={id}

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

Controller: KeyWordsController.index()getKeywordsByAdGroupId()

Model Method: KeywordsModel.getKeywordsByAdGroupId(customerAccountID, customerID, adGroupID)

SOAP Operation: GetKeywordsByAdGroupId

Request:

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

Query Parameters:

ParameterTypeRequiredDescription
adGroupIDIntegerAd group ID to filter keywords
customerAccountIDIntegerAccount ID
customerIDIntegerCustomer ID (optional)

JSON Response:

{
"success": true,
"message": "SUCCESS",
"data": [
{
"Id": 444555666,
"AdGroupId": 111222333,
"Text": "quality products",
"MatchType": "Phrase",
"Bid": {
"Amount": 2.50
},
"Status": "Active",
"EditorialStatus": "Active",
"DestinationUrl": "https://example.com/products",
"FinalUrls": {
"string": ["https://example.com/products"]
},
"Param1": null,
"Param2": null,
"Param3": null
},
{
"Id": 777888999,
"AdGroupId": 111222333,
"Text": "best deals",
"MatchType": "Exact",
"Bid": {
"Amount": 3.00
},
"Status": "Active",
"EditorialStatus": "Active"
}
]
}

Keyword Object Fields:

FieldTypeDescription
IdIntegerKeyword ID
AdGroupIdIntegerParent ad group ID
TextStringKeyword text (search query)
MatchTypeStringExact, Phrase, Broad
Bid.AmountDecimalMaximum CPC bid
StatusStringActive, Paused, Deleted
EditorialStatusStringEditorial review status
DestinationUrlStringLegacy destination URL
FinalUrlsObjectArray of final destination URLs
Param1, Param2, Param3String/NullDynamic text substitution parameters

Side Effects:

  • None (read-only operation)

Get Keyword by ID

GET /keywords/{keywordID}

Purpose: Retrieve specific keyword by ID

Controller: KeyWordsController.show()

Model Method: KeywordsModel.getKeywordsById(customerAccountID, customerID, adGroupID, keywordID)

SOAP Operation: GetKeywordsByIds

Request:

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

Query Parameters:

ParameterTypeRequiredDescription
keywordIDIntegerSpecific keyword ID
adGroupIDIntegerParent ad group ID
customerAccountIDIntegerAccount ID
customerIDIntegerCustomer ID

Validation:

  • If any required param missing, returns error: "Please provide customerAccountID,customerID,adGroupID and keywordID"

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">GetKeywordsByIds</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>
<GetKeywordsByIdsRequest xmlns="https://bingads.microsoft.com/CampaignManagement/v13">
<AdGroupId>111222333</AdGroupId>
<KeywordIds i:nil="false" xmlns:a1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a1:long>444555666</a1:long>
</KeywordIds>
</GetKeywordsByIdsRequest>
</s:Body>
</s:Envelope>

Helper Method: generateKeywordsIdsXml(keywordID)

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

JSON Response:

{
"success": true,
"message": "SUCCESS",
"data": {
"Keywords": {
"Id": 444555666,
"AdGroupId": 111222333,
"Text": "quality products",
"MatchType": "Phrase",
"Bid": {
"Amount": 2.50
},
"Status": "Active",
"EditorialStatus": "Active"
}
}
}

Get Keyword Performance Metrics

GET /keywords/metrics

Purpose: Generate asynchronous performance report for keywords with quality scores

Controller: KeyWordsController.metrics()

Model Method: KeywordsModel.getKeywordReport(...)

SOAP Operation: SubmitGenerateReport

Service: Reporting Service v13

Request:

GET /v1/integrations/bing/ads/keywords/metrics?customerAccountID=111222333&customerID=123456&campaignID=987654321&campaignType=Search&adGroupID=111222333&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
keywordIDIntegerSpecific keyword IDAll keywords
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="KeywordPerformanceReportRequest">
<ExcludeColumnHeaders>false</ExcludeColumnHeaders>
<ExcludeReportFooter>false</ExcludeReportFooter>
<ExcludeReportHeader>false</ExcludeReportHeader>
<Format>Csv</Format>
<Language>English</Language>
<ReportName>KeywordPerformanceReportRequest</ReportName>
<ReturnOnlyCompleteData>false</ReturnOnlyCompleteData>
<Aggregation>Daily</Aggregation>
<Columns>
<KeywordPerformanceReportColumn>AccountName</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>Keyword</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>KeywordId</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>MatchType</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>Impressions</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>Clicks</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>QualityScore</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>ExpectedCtr</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>AdRelevance</KeywordPerformanceReportColumn>
<KeywordPerformanceReportColumn>LandingPageExperience</KeywordPerformanceReportColumn>
<!-- 67+ more columns available -->
</Columns>
<!-- Optional: Filter by specific keyword -->
<Filter i:nil="false">
<Keywords i:nil="false" xmlns:a1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<a1:string>444555666</a1:string>
</Keywords>
</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>
<CampaignType>Search</CampaignType>
</CampaignReportScope>
</Campaigns>
</Scope>
<Time>
<PredefinedTime i:nil="false">Last7Days</PredefinedTime>
<ReportTimeZone>EasternTimeUSCanada</ReportTimeZone>
</Time>
</ReportRequest>
</SubmitGenerateReportRequest>
</s:Body>
</s:Envelope>

Available Report Columns (73 total - hardcoded in source):

ColumnDescription
AccountName, AccountNumber, AccountIdAccount identifiers
CampaignName, CampaignId, CampaignStatusCampaign info
AdGroupName, AdGroupId, AdGroupStatusAd group info
Keyword, KeywordId, KeywordStatusKeyword info
AdId, AdType, DestinationUrlAssociated ad info
BidMatchType, DeliveredMatchTypeMatch type information
CurrentMaxCpcCurrent maximum CPC bid
Impressions, Clicks, CtrCore metrics
Spend, AverageCpc, AveragePositionCost metrics
Conversions, ConversionRate, CostPerConversionConversion metrics
Revenue, ReturnOnAdSpend, RevenuePerConversionRevenue metrics
QualityScoreOverall keyword quality (1-10)
ExpectedCtrExpected CTR component (1-3)
AdRelevanceAd relevance component (1-3)
LandingPageExperienceLanding page component (1-3)
QualityImpactQuality score impact
DeviceType, DeviceOS, LanguageTargeting dimensions
Network, TopVsOtherNetwork placement
BidStrategyTypeBidding strategy type
Mainline1Bid, MainlineBid, FirstPageBidBid estimates
AllConversions, AllRevenue, ViewThroughConversionsAdvanced metrics

Note: All 73 columns are hardcoded in the XML request (see source code lines 255-325)

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

🎯 Match Types

Match TypeDescriptionExample KeywordTriggers For
ExactExact match only[quality products]"quality products" only
PhrasePhrase match with close variants"quality products""buy quality products", "quality products online"
BroadBroad match with variationsquality products"high quality items", "product quality", "best products"

📊 Quality Score Components

Overall Quality Score: 1-10 scale (10 is best)

Three Components (each scored 1-3):

ComponentDescriptionImpact
Expected CTRPredicted click-through rateHow likely ad will be clicked
Ad RelevanceHow well ad matches keywordAd copy relevance to search
Landing Page ExperienceLanding page quality and relevanceUser experience after click

Score Meanings:

  • 3 - Above Average (good)
  • 2 - Average (acceptable)
  • 1 - Below Average (needs improvement)

💰 Bid Estimates

Bid TypeDescription
Mainline1BidEstimated bid for top position (mainline 1)
MainlineBidEstimated bid for mainline (top section)
FirstPageBidEstimated bid for first page (any position)

These estimates help optimize bids for desired ad positions.

📝 Status Values

Keyword Status

StatusDescription
ActiveKeyword is active and eligible to trigger ads
PausedKeyword is manually paused
DeletedKeyword is deleted (soft delete)

Editorial Status

StatusDescription
ActiveApproved and serving
ActiveLimitedServing with limitations
DisapprovedRejected by editorial review
InactiveNot serving (paused or deleted)

🔄 Helper Methods

generateKeywordsIdsXml(keywordID)

Converts comma-separated keyword IDs to SOAP XML format.

Input: "444555666,777888999"

Output:

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

⚠️ Important Notes

  • 🚫 No Direct Campaign Query: Cannot get keywords 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: Keywords always belong to ad groups
  • 🔢 Quality Score: Available only in reporting, not in keyword retrieval
  • 📊 73 Columns: All hardcoded in source (no dynamic column selection)
  • 🌍 Timezone: Affects date range interpretation
  • 🔐 Four IDs Required: customerAccountID, customerID, adGroupID, keywordID for specific keyword 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, or DynamicSearchAds
  • 🎨 Dynamic Parameters: Param1, Param2, Param3 for dynamic text substitution
  • Promise.all(): Uses parallel execution for multiple ad group queries
  • 🎯 Keyword Filter: Can filter reports by specific keyword ID in Filter element
💬

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