Integrate Bolt.new with Microsoft Advertising (Bing Ads) using the Reporting and Campaign Management APIs via Next.js API routes. Register a Microsoft Advertising developer app, implement OAuth 2.0 with Azure AD to obtain tokens, then fetch search campaign performance data including impressions, clicks, spend, and quality scores. Build a Bing Ads dashboard. The API primarily uses REST for OAuth and SOAP for bulk operations — the bing-ads Node.js SDK handles the SOAP complexity.
Build a Microsoft Advertising (Bing Ads) Dashboard in Bolt.new
Microsoft Advertising reaches users on Bing, Yahoo, MSN, and the broader Microsoft Audience Network — a search engine audience that skews older, more affluent, and more likely to be in B2B decision-making roles than Google's broader demographic. Bing holds approximately 6-8% of US search market share, but that translates to hundreds of millions of monthly searches and often lower cost-per-click than equivalent Google campaigns since fewer advertisers compete for the same keywords. For advertisers who have exhausted Google's efficiency and need additional search volume, Microsoft Advertising is often the next priority channel.
The Microsoft Advertising API has a mixed architecture: OAuth 2.0 (REST) for authentication, and primarily SOAP-based web services for campaign management and reporting. The reporting flow works by submitting a report request (which returns a request ID), polling for completion, then downloading the completed report from a URL. This asynchronous pattern differs from Meta's synchronous API responses and requires patience in your implementation. The `bing-ads` npm package abstracts much of this SOAP complexity into JavaScript promises.
For developers building a Bolt dashboard, the good news is that Microsoft offers an OAuth 2.0 device code flow that allows obtaining tokens without a browser redirect — you just visit a URL, enter a code, and the token is issued to your application. This means you can get authentication working entirely within Bolt's WebContainer preview environment without deploying first. The initial credential setup is more involved than Facebook or Reddit, but the resulting dashboard gives you access to search campaign data unavailable elsewhere.
Integration method
Bolt generates Next.js API routes that call Microsoft Advertising's REST and SOAP APIs, keeping OAuth 2.0 credentials server-side. Outbound API calls for fetching campaign data work in Bolt's WebContainer during development. OAuth 2.0 token refresh can be automated server-side, but the initial authorization flow requires a deployed redirect URI. Use the 'device code flow' during development to obtain the first refresh token without deploying.
Prerequisites
- A Bolt.new account with a Next.js project
- A Microsoft Advertising account at ads.microsoft.com with at least one active campaign
- A Microsoft account (personal or work/Azure AD) for OAuth authentication
- A registered app in Azure Active Directory or Microsoft Advertising developer portal
- The bing-ads npm package installed in the Bolt project
Step-by-step guide
Register a Microsoft Advertising Developer App
Register a Microsoft Advertising Developer App
Microsoft Advertising API authentication uses Azure Active Directory (Azure AD) OAuth 2.0. You register your application once, then use the resulting Client ID and optionally a Client Secret to obtain access tokens for any Microsoft Advertising account that grants your app permission. Go to the Microsoft Azure Portal at portal.azure.com (or portal.microsoftonline.com) and sign in with a Microsoft account. Navigate to 'Azure Active Directory' → 'App registrations' → 'New registration.' Give the app a name (e.g., 'Bolt Ads Dashboard'), choose the supported account type (for personal Microsoft accounts, choose 'Accounts in any organizational directory and personal Microsoft accounts'), and enter a redirect URI — use `http://localhost:3000/auth/callback` for initial setup. After creating the app, you see the Application (client) ID — this is your `MICROSOFT_ADS_CLIENT_ID`. Navigate to 'Certificates & secrets' → 'New client secret.' Set an expiration (24 months is reasonable) and save the secret value immediately — it is only shown once. This is your `MICROSOFT_ADS_CLIENT_SECRET`. With the Azure app registered, you still need to authorize it to call Microsoft Advertising. This is done by linking it to a Microsoft Advertising account. The Microsoft Advertising developer docs describe a 'consent' flow where an account user authorizes the app. For your own account, this consent happens automatically during the OAuth authorization flow. Your Microsoft Advertising credentials you need: the Customer ID (visible in the top-right of Microsoft Advertising interface as 'Customer ID'), the Account ID (also called 'Account number', visible in account settings), and the Access Token (obtained via OAuth flow). Add these to your Bolt project's .env file.
Install the bing-ads npm package. Create a .env file with MICROSOFT_ADS_CLIENT_ID, MICROSOFT_ADS_CLIENT_SECRET, MICROSOFT_ADS_REFRESH_TOKEN, MICROSOFT_ADS_CUSTOMER_ID, and MICROSOFT_ADS_ACCOUNT_ID. Create lib/bing-ads.ts that exports a getMicrosoftAdsToken function that calls https://login.microsoftonline.com/common/oauth2/v2.0/token with grant_type=refresh_token using the MICROSOFT_ADS_REFRESH_TOKEN and client credentials, and returns the access_token string. Export a microsoftAdsHeaders helper that returns Authorization Bearer headers with a fresh token.
Paste this in Bolt.new chat
1// lib/bing-ads.ts2const TOKEN_ENDPOINT = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';3const SCOPE = 'https://ads.microsoft.com/ads.manage offline_access';45let cachedToken: { token: string; expiresAt: number } | null = null;67export async function getMicrosoftAdsToken(): Promise<string> {8 // Return cached token if not expired (with 60s buffer)9 if (cachedToken && Date.now() < cachedToken.expiresAt - 60_000) {10 return cachedToken.token;11 }1213 const clientId = process.env.MICROSOFT_ADS_CLIENT_ID;14 const clientSecret = process.env.MICROSOFT_ADS_CLIENT_SECRET;15 const refreshToken = process.env.MICROSOFT_ADS_REFRESH_TOKEN;1617 if (!clientId || !clientSecret || !refreshToken) {18 throw new Error(19 'Missing Microsoft Advertising credentials. Set MICROSOFT_ADS_CLIENT_ID, ' +20 'MICROSOFT_ADS_CLIENT_SECRET, and MICROSOFT_ADS_REFRESH_TOKEN in .env'21 );22 }2324 const response = await fetch(TOKEN_ENDPOINT, {25 method: 'POST',26 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },27 body: new URLSearchParams({28 client_id: clientId,29 client_secret: clientSecret,30 refresh_token: refreshToken,31 grant_type: 'refresh_token',32 scope: SCOPE,33 }),34 });3536 const data = await response.json() as {37 access_token?: string;38 expires_in?: number;39 error?: string;40 error_description?: string;41 };4243 if (!response.ok || !data.access_token) {44 throw new Error(`Token refresh failed: ${data.error_description || data.error}`);45 }4647 cachedToken = {48 token: data.access_token,49 expiresAt: Date.now() + (data.expires_in || 3600) * 1000,50 };5152 return cachedToken.token;53}5455export async function getMicrosoftAdsHeaders(): Promise<Record<string, string>> {56 const token = await getMicrosoftAdsToken();57 const customerId = process.env.MICROSOFT_ADS_CUSTOMER_ID;58 const accountId = process.env.MICROSOFT_ADS_ACCOUNT_ID;5960 return {61 Authorization: `Bearer ${token}`,62 'CustomerId': customerId || '',63 'CustomerAccountId': accountId || '',64 'Content-Type': 'application/json',65 'DeveloperToken': process.env.MICROSOFT_ADS_DEVELOPER_TOKEN || '',66 };67}Pro tip: Microsoft Advertising also requires a Developer Token (separate from the OAuth token) in every API request header. Get this from the Microsoft Advertising developer portal at ads.microsoft.com → Tools → API Center. A Universal Developer Token works for all accounts without a sandbox restriction.
Expected result: The lib/bing-ads.ts helper is in place. The token refresh function works when MICROSOFT_ADS_REFRESH_TOKEN is valid. A test call to getMicrosoftAdsToken() should return a non-empty access token string.
Obtain the Initial Refresh Token Using Device Code Flow
Obtain the Initial Refresh Token Using Device Code Flow
Microsoft's device code flow lets you obtain OAuth tokens without a browser redirect — perfect for Bolt's WebContainer environment where redirect URIs cannot be registered. Instead of your app redirecting to Microsoft, you visit a Microsoft URL in any browser on any device, enter a short code, and the token is issued to your application. To use the device code flow, make a POST request to `https://login.microsoftonline.com/common/oauth2/v2.0/devicecode` with your `client_id` and `scope` parameters. The response includes a `user_code` (e.g., `ABCD1234`) and a `verification_uri` (usually `https://microsoft.com/devicelogin`). Open the verification URI in your browser, sign in with your Microsoft account, and enter the user code when prompted. Authorize the app to access your Microsoft Advertising data. While you complete the browser step, your application polls `https://login.microsoftonline.com/common/oauth2/v2.0/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code` and the `device_code` from the first response, checking every 5 seconds until authentication completes. When you finish authorizing in the browser, the token endpoint returns an `access_token` and a `refresh_token`. Save the `refresh_token` value to your .env file as `MICROSOFT_ADS_REFRESH_TOKEN`. This refresh token is long-lived (Microsoft Advertising refresh tokens are valid for 90 days with rolling expiry — each use extends validity by another 90 days). As long as your dashboard is actively used, the refresh token effectively never expires. This device code flow is also suitable for production use by individual advertisers who want to connect their own Microsoft Advertising account. For multi-tenant applications where multiple users connect their accounts, implement the standard redirect-based authorization code flow using your deployed URL.
Create a Next.js API route at app/api/bing/device-auth/route.ts with a GET handler that initiates the device code flow. POST to https://login.microsoftonline.com/common/oauth2/v2.0/devicecode with MICROSOFT_ADS_CLIENT_ID and scope='https://ads.microsoft.com/ads.manage offline_access'. Return { userCode, verificationUri, deviceCode, message: 'Visit verificationUri, enter userCode, then call /api/bing/device-auth/token' }. Create a second endpoint at /api/bing/device-auth/token with GET that polls the token endpoint with the device_code from query params and returns the refresh_token when authorization completes.
Paste this in Bolt.new chat
1// app/api/bing/device-auth/route.ts2import { NextResponse } from 'next/server';34export async function GET() {5 const clientId = process.env.MICROSOFT_ADS_CLIENT_ID;6 if (!clientId) {7 return NextResponse.json({ error: 'MICROSOFT_ADS_CLIENT_ID not configured' }, { status: 500 });8 }910 const response = await fetch(11 'https://login.microsoftonline.com/common/oauth2/v2.0/devicecode',12 {13 method: 'POST',14 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },15 body: new URLSearchParams({16 client_id: clientId,17 scope: 'https://ads.microsoft.com/ads.manage offline_access',18 }),19 }20 );2122 const data = await response.json() as {23 user_code?: string;24 device_code?: string;25 verification_uri?: string;26 expires_in?: number;27 interval?: number;28 error?: string;29 };3031 if (!response.ok || !data.device_code) {32 return NextResponse.json({ error: data.error || 'Device code request failed' }, { status: 500 });33 }3435 return NextResponse.json({36 userCode: data.user_code,37 verificationUri: data.verification_uri,38 deviceCode: data.device_code,39 expiresIn: data.expires_in,40 message: `1. Visit ${data.verification_uri} in your browser. 2. Enter code: ${data.user_code}. 3. Call /api/bing/device-auth/token?deviceCode=${data.device_code} to get your refresh token.`,41 });42}Pro tip: After you copy the refresh token from the device auth flow and add it to .env, you do not need to run the device code flow again unless the refresh token expires (90 days of inactivity) or you revoke app access. Delete the device-auth routes before deploying to production — they are only needed for the initial setup.
Expected result: Calling /api/bing/device-auth returns a user code and verification URL. After completing the authorization in your browser, calling /api/bing/device-auth/token returns a refresh token that you can save to .env as MICROSOFT_ADS_REFRESH_TOKEN.
Fetch Campaign Performance Reports
Fetch Campaign Performance Reports
Microsoft Advertising's Reporting API uses an asynchronous pattern: you submit a report request, receive a request ID, poll for completion, then download the completed CSV or XML report from a URL. This is different from synchronous APIs like Facebook's Marketing API or Reddit's Ads API, which return data immediately. The reporting flow involves three API calls: first, `POST` to the Reporting service to submit a report request with your desired columns, date range, and filtering. The response contains a `ReportRequestId`. Second, `GET` the report status by calling the same endpoint with the request ID until the status changes from `Pending` or `Running` to `Success`. Third, fetch the report file from the download URL in the success response. For Next.js API routes in Bolt, implement this flow with a polling timeout: submit the request, then poll with a maximum of 6 attempts every 5 seconds (30 seconds total). For most dashboards with modest data volumes, reports complete in under 10 seconds. If the report is not ready within 30 seconds, return a 202 Accepted response with the request ID, and have the client poll your API route again. The Reporting API is SOAP-based, and the bing-ads npm package provides JavaScript wrappers. Alternatively, you can call the REST reporting endpoint `https://reporting.api.bingads.microsoft.com/Reporting/v13/GenerateReport` with a JSON body — Microsoft added REST support for reporting endpoints. The REST approach is simpler for Bolt-generated code and does not require understanding SOAP envelope formatting. Key report columns for a campaign performance dashboard: `CampaignName`, `CampaignId`, `CampaignStatus`, `Spend`, `Impressions`, `Clicks`, `AverageCpc`, `Ctr`, `AverageCpm`, `QualityScore`, `Conversions`, `ConversionRate`, `CostPerConversion`, `Revenue`, `RevenuePerConversion`. The `QualityScore` metric (1-10 scale) is unique to Microsoft Advertising and measures how relevant your ads are to searchers — high quality scores reduce your cost per click.
Create a Next.js API route at app/api/bing/campaigns/route.ts that fetches campaign performance from Microsoft Advertising Reporting API. Accept startDate and endDate query params (YYYY-MM-DD format, default last 30 days). Submit a CampaignPerformanceReportRequest to https://reporting.api.bingads.microsoft.com/Reporting/v13/GenerateReport with columns: CampaignName, CampaignId, CampaignStatus, Spend, Impressions, Clicks, AverageCpc, Ctr, QualityScore, Conversions. Poll for completion (max 30 seconds, 5 second intervals). Parse the TSV response and return as JSON array. Use getMicrosoftAdsHeaders from lib/bing-ads.ts.
Paste this in Bolt.new chat
1// app/api/bing/campaigns/route.ts2import { NextResponse } from 'next/server';3import { getMicrosoftAdsHeaders } from '@/lib/bing-ads';45const REPORTING_BASE = 'https://reporting.api.bingads.microsoft.com/Reporting/v13';67async function submitReportRequest(8 headers: Record<string, string>,9 startDate: string,10 endDate: string,11 accountId: string12): Promise<string> {13 const [startYear, startMonth, startDay] = startDate.split('-').map(Number);14 const [endYear, endMonth, endDay] = endDate.split('-').map(Number);1516 const body = {17 ReportRequest: {18 Format: 'Tsv',19 Language: 'English',20 ReportName: 'CampaignPerformance',21 ReturnOnlyCompleteData: false,22 Type: 'CampaignPerformanceReport',23 Aggregation: 'Summary',24 Columns: [25 'CampaignName', 'CampaignId', 'CampaignStatus',26 'Spend', 'Impressions', 'Clicks', 'AverageCpc',27 'Ctr', 'QualityScore', 'Conversions', 'CostPerConversion',28 ],29 Scope: { AccountIds: [parseInt(accountId, 10)] },30 Time: {31 CustomDateRangeStart: { Day: startDay, Month: startMonth, Year: startYear },32 CustomDateRangeEnd: { Day: endDay, Month: endMonth, Year: endYear },33 },34 },35 };3637 const response = await fetch(`${REPORTING_BASE}/GenerateReport`, {38 method: 'POST',39 headers,40 body: JSON.stringify(body),41 });4243 const data = await response.json() as { ReportRequestId?: string; Error?: { Message: string } };44 if (!data.ReportRequestId) {45 throw new Error(data.Error?.Message || 'Failed to submit report request');46 }47 return data.ReportRequestId;48}4950async function pollReport(51 headers: Record<string, string>,52 requestId: string,53 maxAttempts = 654): Promise<string> {55 for (let attempt = 0; attempt < maxAttempts; attempt++) {56 if (attempt > 0) await new Promise((r) => setTimeout(r, 5000));5758 const response = await fetch(59 `${REPORTING_BASE}/PollGenerateReport?ReportRequestId=${requestId}`,60 { headers }61 );6263 const data = await response.json() as {64 ReportRequestStatus?: { Status: string; ReportDownloadUrl?: string };65 };6667 const status = data.ReportRequestStatus?.Status;68 if (status === 'Success' && data.ReportRequestStatus?.ReportDownloadUrl) {69 return data.ReportRequestStatus.ReportDownloadUrl;70 }71 if (status === 'Error') throw new Error('Report generation failed');72 }73 throw new Error('Report timed out after 30 seconds');74}7576function parseTsv(tsv: string): Record<string, string>[] {77 const lines = tsv.trim().split('\n');78 const headers = lines[1]?.split('\t') || []; // Line 0 = report metadata, Line 1 = headers79 return lines.slice(2).map((line) => {80 const values = line.split('\t');81 return Object.fromEntries(headers.map((h, i) => [h.trim(), (values[i] || '').trim()]));82 }).filter((row) => row['Campaign name'] || row['CampaignName']);83}8485export async function GET(request: Request) {86 const { searchParams } = new URL(request.url);87 const accountId = process.env.MICROSOFT_ADS_ACCOUNT_ID;88 if (!accountId) {89 return NextResponse.json({ error: 'MICROSOFT_ADS_ACCOUNT_ID not configured' }, { status: 500 });90 }9192 const endDate = new Date().toISOString().split('T')[0];93 const startDate = new Date(Date.now() - 30 * 86400000).toISOString().split('T')[0];9495 try {96 const headers = await getMicrosoftAdsHeaders();97 const requestId = await submitReportRequest(98 headers,99 searchParams.get('startDate') || startDate,100 searchParams.get('endDate') || endDate,101 accountId102 );103104 const downloadUrl = await pollReport(headers, requestId);105 const reportText = await fetch(downloadUrl).then((r) => r.text());106 const rows = parseTsv(reportText);107108 const campaigns = rows.map((row) => ({109 name: row['CampaignName'] || row['Campaign name'] || '',110 id: row['CampaignId'] || row['Campaign ID'] || '',111 status: row['CampaignStatus'] || row['Campaign status'] || '',112 spend: parseFloat(row['Spend'] || '0'),113 impressions: parseInt(row['Impressions'] || '0', 10),114 clicks: parseInt(row['Clicks'] || '0', 10),115 avgCpc: parseFloat(row['AverageCpc'] || row['Avg. CPC'] || '0'),116 ctr: parseFloat(row['Ctr'] || row['CTR'] || '0'),117 qualityScore: parseInt(row['QualityScore'] || row['Quality score'] || '0', 10),118 conversions: parseInt(row['Conversions'] || '0', 10),119 costPerConversion: parseFloat(row['CostPerConversion'] || row['Cost per conversion'] || '0'),120 }));121122 campaigns.sort((a, b) => b.spend - a.spend);123124 const totals = campaigns.reduce(125 (acc, c) => ({ spend: acc.spend + c.spend, impressions: acc.impressions + c.impressions, clicks: acc.clicks + c.clicks, conversions: acc.conversions + c.conversions }),126 { spend: 0, impressions: 0, clicks: 0, conversions: 0 }127 );128129 return NextResponse.json({ campaigns, totals });130 } catch (err) {131 const message = err instanceof Error ? err.message : 'Failed to fetch Bing Ads data';132 return NextResponse.json({ error: message }, { status: 500 });133 }134}Pro tip: Microsoft Advertising reports have a two-row header — the first row is metadata about the report, the second row is column headers. Parse starting from line index 1 for headers and line index 2 for data. The exact column names in the TSV may differ slightly from the requested column names in your report definition.
Expected result: The campaigns API route submits a report request, polls for completion, and parses the TSV response into a JSON array of campaign objects with numeric spend and impression values. Verify with /api/bing/campaigns in the browser.
Build the Bing Ads Analytics Dashboard
Build the Bing Ads Analytics Dashboard
A Microsoft Advertising dashboard has several unique data points to surface compared to Facebook or Reddit dashboards. The most distinctive is Quality Score — a 1-10 composite rating of how relevant your keyword, ad copy, and landing page are to searchers. Quality Score directly affects your cost per click: a QS of 10 can reduce your actual CPC to well below your bid, while a QS of 1-3 can increase costs significantly. Highlighting campaigns and keywords with low quality scores is high-value for Microsoft Advertising users. Microsoft Advertising also provides search term data showing the actual queries that triggered your ads, auction insights comparing your impression share against competitors, and demographic performance data powered by LinkedIn integration for B2B targeting. These are all separate report requests that can be added as drill-down views in your dashboard. For the main dashboard layout: a summary header with total spend, total impressions, total clicks, and average CTR; a quality score distribution widget (how many campaigns are in QS ranges 1-3, 4-6, 7-10); a sortable campaign table with a QS column that uses color coding (red for 1-4, yellow for 5-7, green for 8-10); and a date range picker. Network data in Microsoft Advertising distinguishes between Bing and partner search (Yahoo, AOL, DuckDuckGo) placements, as well as the Microsoft Audience Network. If you request the Network column in your report, you can show performance broken down by search vs. audience network, which often shows very different CPC and conversion rates. Audience Network typically has lower CPCs but also lower conversion rates. For visualizations, a bar chart comparing spend by campaign and a scatter plot of Quality Score vs. CPC (showing that higher quality score correlates with lower CPC) are the most actionable charts for Microsoft Advertising optimization.
Build a Bing Ads dashboard page at /bing-ads that fetches from /api/bing/campaigns with date range params (default last 30 days). Show: (1) summary cards for total spend, total clicks, average CTR, and total conversions; (2) a quality score distribution widget showing campaign counts in Low (1-4), Medium (5-7), High (8-10) ranges as colored segments; (3) a sortable campaign table with Name, Status badge, Spend, Impressions, Clicks, CTR, Avg CPC, Quality Score (color-coded), and Conversions columns. Add Last 7d / Last 30d / Last 90d date presets. Include loading skeletons and error state.
Paste this in Bolt.new chat
Pro tip: Microsoft Advertising Quality Score is only calculated for keywords with sufficient impression volume. Campaigns or keywords with fewer than 1,000 impressions in the last 30 days may show a QS of 0 or '--'. Handle this by displaying 'N/A' rather than '0' for low-volume entries.
Expected result: The Bing Ads dashboard loads with real Microsoft Advertising campaign data, displays Quality Score with color-coded indicators, and the date range selector correctly updates the data display.
Deploy and Configure Production Access
Deploy and Configure Production Access
During development in Bolt's WebContainer, all outbound calls to Microsoft Advertising's Reporting API work correctly since they are HTTPS requests. The asynchronous report polling also works in the WebContainer — the setTimeout delays in the polling loop function normally in Node.js runtime. There is no incoming webhook or OAuth redirect that requires a deployed server for the read-only reporting integration. Deploy to Netlify via Settings → Applications → Connect Netlify, or click Publish to deploy to Bolt Cloud. In your hosting dashboard, add the server-side environment variables without `NEXT_PUBLIC_` prefix: `MICROSOFT_ADS_CLIENT_ID`, `MICROSOFT_ADS_CLIENT_SECRET`, `MICROSOFT_ADS_REFRESH_TOKEN`, `MICROSOFT_ADS_CUSTOMER_ID`, `MICROSOFT_ADS_ACCOUNT_ID`, and `MICROSOFT_ADS_DEVELOPER_TOKEN`. For production with the redirect-based OAuth flow (for multi-user SaaS where each user connects their own Microsoft Advertising account), update your Azure AD app registration to add your production domain as a redirect URI. Go to Azure Portal → App registrations → your app → Authentication → Add a redirect URI: `https://your-app.netlify.app/api/bing/auth/callback`. Microsoft Advertising's refresh tokens have a 90-day rolling expiry — each use extends the expiry by 90 days. If your dashboard is actively used daily or weekly, the refresh token effectively never expires. However, if the application is inactive for 90 days, users will need to re-authorize. Build a re-authorization flow that detects refresh token failures and prompts the user to re-connect their Microsoft Advertising account.
Add an /api/bing/health route that checks all required env vars are configured, then calls the Microsoft Advertising token endpoint to verify the refresh token is valid and gets a fresh access token. Return { connected: true, expiresIn } on success or { connected: false, error } if the token refresh fails. Show this status on the Bing Ads dashboard settings panel.
Paste this in Bolt.new chat
1# Production environment variables — add to Netlify or Bolt Cloud2# All must be server-side only (no NEXT_PUBLIC_ prefix)34MICROSOFT_ADS_CLIENT_ID=your_azure_app_client_id5MICROSOFT_ADS_CLIENT_SECRET=your_azure_app_client_secret6MICROSOFT_ADS_REFRESH_TOKEN=your_refresh_token_from_device_flow7MICROSOFT_ADS_CUSTOMER_ID=your_customer_id8MICROSOFT_ADS_ACCOUNT_ID=your_account_id9MICROSOFT_ADS_DEVELOPER_TOKEN=your_developer_tokenPro tip: Microsoft Advertising Developer Tokens come in two types: sandbox tokens (for testing with sandbox accounts) and universal tokens (for production). Ensure you are using a Universal Developer Token from the production environment, not a sandbox token, when accessing real campaign data.
Expected result: The app is deployed with all Microsoft Advertising credentials configured as server-side environment variables. The health check confirms successful token refresh and the dashboard loads real campaign data from the deployed URL.
Common use cases
Bing Search Campaign Performance Dashboard
Build a dashboard displaying Microsoft Advertising campaign performance across all active search campaigns on Bing, Yahoo, and MSN. Show spend, impressions, clicks, average CPC, quality score, and conversion data with date range filtering. Compare performance against equivalent Google Ads campaigns to understand the Microsoft Advertising ROI differential.
Build a Microsoft Advertising campaign dashboard. Create a Next.js API route at /api/bing/campaigns that fetches campaign performance using the Microsoft Advertising Reporting API. Use MICROSOFT_ADS_ACCESS_TOKEN, MICROSOFT_ADS_CUSTOMER_ID, and MICROSOFT_ADS_ACCOUNT_ID from process.env. Request a CampaignPerformanceReport with date range from query params, columns: CampaignName, Spend, Impressions, Clicks, AverageCpc, Ctr, QualityScore, Conversions. Poll for report completion (max 30s) then parse and return the results. Build a React dashboard with a summary header and sortable campaign table.
Copy this prompt to try it in Bolt.new
Keyword Performance and Quality Score Analyzer
Analyze keyword-level performance data to identify high-performing search terms, keywords with poor quality scores that are inflating costs, and negative keyword opportunities. Build a keyword optimizer view that surfaces quality score improvements alongside bid recommendations for the most impactful changes.
Create a keyword analyzer using Microsoft Advertising Reporting API. Build a Next.js API route at /api/bing/keywords that requests a KeywordPerformanceReport with columns: Keyword, CampaignName, AdGroupName, Impressions, Clicks, Spend, AverageCpc, QualityScore, ExpectedCtr, AdRelevance, LandingPageExperience, Conversions. Parse and return keywords sorted by spend descending. Build a React table highlighting keywords with QualityScore below 5 in red and above 7 in green. Show the three QualityScore components as separate columns. Use MICROSOFT_ADS_ACCESS_TOKEN, MICROSOFT_ADS_CUSTOMER_ID, MICROSOFT_ADS_ACCOUNT_ID from process.env.
Copy this prompt to try it in Bolt.new
Microsoft Audience Network Performance Tracker
Track performance of Microsoft Audience Ads — native ads shown across MSN, Outlook, and the Microsoft Audience Network. These audience ads differ from search ads in their targeting (based on LinkedIn demographic data for B2B targeting) and creative format. Build a dashboard comparing search vs. audience network performance for the same budget.
Build a Microsoft Audience Network tracker. Create a Next.js API route at /api/bing/audience that requests an AudiencePerformanceReport from Microsoft Advertising API with columns: CampaignName, AdGroupName, Impressions, Clicks, Spend, Ctr, AverageCpc, Conversions, Network (to filter for AUDIENCE_ADS). Build a React component showing audience network campaigns separately from search campaigns, with a spend comparison bar chart. Highlight the cost-per-conversion difference between search and audience placements. Store credentials in process.env.
Copy this prompt to try it in Bolt.new
Troubleshooting
Token refresh fails with 'AADSTS70011: The provided value for the input parameter scope is not valid'
Cause: The OAuth scope string for Microsoft Advertising is specific — it must be exactly 'https://ads.microsoft.com/ads.manage offline_access'. Any variation, including extra spaces or different scope values, causes Azure AD to reject the token request.
Solution: Verify the scope parameter in your token refresh call is exactly 'https://ads.microsoft.com/ads.manage offline_access' with a single space between the two scope values. The scope string must be consistent between the initial authorization and all refresh token requests.
1// Correct scope string:2const SCOPE = 'https://ads.microsoft.com/ads.manage offline_access';3// In URLSearchParams:4body: new URLSearchParams({5 grant_type: 'refresh_token',6 scope: SCOPE, // must match exactly7 refresh_token: process.env.MICROSOFT_ADS_REFRESH_TOKEN || '',8 client_id: process.env.MICROSOFT_ADS_CLIENT_ID || '',9 client_secret: process.env.MICROSOFT_ADS_CLIENT_SECRET || '',10})Report request returns Error 105 'Invalid developer token' or 106 'Customer is not authorized to use the API'
Cause: The Developer Token in the request header is missing, incorrect, or is a sandbox token being used against the production API (or vice versa). The Developer Token is separate from the OAuth access token.
Solution: Obtain your Developer Token from Microsoft Advertising at ads.microsoft.com → Tools → API Center. Use a Universal Developer Token for production access. Ensure the token is included in every API request header as 'DeveloperToken'. If using a sandbox token, switch to the sandbox endpoint at https://sandbox.bingads.microsoft.com.
1// Include DeveloperToken in all request headers:2const headers = {3 'Authorization': `Bearer ${accessToken}`,4 'DeveloperToken': process.env.MICROSOFT_ADS_DEVELOPER_TOKEN || '',5 'CustomerId': process.env.MICROSOFT_ADS_CUSTOMER_ID || '',6 'CustomerAccountId': process.env.MICROSOFT_ADS_ACCOUNT_ID || '',7};Report returns empty data or only header rows despite active campaigns with spend
Cause: The date range in the report request may reference dates before the account was created, the AccountIds array may contain the wrong account ID format, or the campaign status filter may exclude your campaigns.
Solution: Verify the AccountIds array contains the numeric account ID (not the account number string). In Microsoft Advertising, Account ID is a numeric integer; the Account Number is a string like 'E271234'. Use the numeric ID in API calls. Also verify ReturnOnlyCompleteData is set to false to include data from the current in-progress day.
1// AccountIds must be numeric integers:2Scope: { AccountIds: [parseInt(process.env.MICROSOFT_ADS_ACCOUNT_ID || '0', 10)] }3// NOT:4Scope: { AccountIds: ['E271234'] } // wrong — account number, not account IDWebContainer preview cannot receive OAuth callback redirects during development
Cause: Microsoft OAuth 2.0 authorization code flow requires a redirect URI where Microsoft sends the authorization code. Bolt's WebContainer uses dynamic URLs that cannot be registered as stable redirect URIs.
Solution: Use the OAuth 2.0 device code flow instead (see Step 2). The device code flow does not require a redirect URI — you visit a URL in any browser, enter a code, and tokens are issued to your polling application. This flow works perfectly in Bolt's WebContainer and is suitable for obtaining the initial refresh token without deploying.
Best practices
- Cache access tokens in a module-level variable with their expiry timestamp — Microsoft access tokens last one hour and there is no reason to call the token endpoint on every request when the cached token is still valid.
- Use the device code flow to obtain your initial refresh token without deploying — this avoids the OAuth redirect URI requirement and lets you test the full integration in Bolt's WebContainer preview.
- Include the QualityScore metric in every campaign performance report — it is a Microsoft Advertising-specific insight that identifies cost reduction opportunities unavailable from any other ad platform.
- Request TSV format (not XML) for report downloads — TSV files are smaller, parse faster in JavaScript, and do not require an XML parser dependency.
- Store all credentials as server-side environment variables without NEXT_PUBLIC_ prefix — the access token, refresh token, client secret, and developer token must never appear in client-side code.
- Implement report timeout handling with a 202 response and request ID — for large accounts with millions of impressions, reports may take longer than 30 seconds, and returning the request ID lets the client poll your API rather than holding the connection open.
- Use Aggregation=Summary in report requests for dashboard totals and Aggregation=Daily for trend charts — these are separate report requests since you cannot get both aggregations in the same report.
- Monitor refresh token health proactively — Microsoft refresh tokens expire after 90 days of inactivity, and a production dashboard that goes unused for 90 days will break. Build a health check that alerts when token refresh fails.
Alternatives
Google Ads has much higher search volume and market share than Bing, making it a higher-priority integration for most advertising dashboards, though Microsoft Advertising typically offers lower CPCs for the same keywords.
Facebook Ads targets users based on demographics and interests rather than search intent, offering a complementary reach strategy with much simpler REST API integration compared to Microsoft Advertising's SOAP-based services.
LinkedIn Ads provide the same professional audience demographics as Microsoft Advertising's LinkedIn-powered targeting, but with more granular B2B targeting options and a REST API that is easier to integrate.
Amazon Advertising targets high-intent shoppers at the moment of product discovery, offering a fundamentally different attribution model from search ads that complements rather than overlaps with Bing Ads reach.
Frequently asked questions
Does Bolt.new have a native Microsoft Advertising (Bing Ads) integration?
No — Bolt.new does not include a native Microsoft Advertising connector. Building the integration requires creating Next.js API routes that call Microsoft Advertising's Reporting API. The main complexity is the asynchronous report request-poll-download pattern and OAuth 2.0 with Azure AD. Bolt's AI can generate much of the boilerplate code from a descriptive prompt.
Can I test the Bing Ads dashboard in Bolt's WebContainer without deploying?
Yes — if you use the device code flow to obtain credentials upfront, all outbound calls to Microsoft Advertising work in Bolt's WebContainer. The report polling logic (setTimeout intervals) also works in the WebContainer's Node.js runtime. The only scenario requiring deployment is the standard OAuth authorization code flow where Microsoft redirects to your app's callback URL.
What is Quality Score in Microsoft Advertising and why does it matter?
Quality Score is a 1-10 rating of how relevant your keywords, ad copy, and landing pages are to the searcher's intent. Higher quality scores reduce your actual cost-per-click below your bid — a Quality Score of 10 can mean paying 50% less than your maximum bid. Conversely, a QS of 1-3 can significantly inflate costs. Quality Score is unique to search advertising and has no equivalent in social ad platforms like Facebook or Reddit.
Why does the Microsoft Advertising API use SOAP instead of REST like most modern APIs?
Microsoft Advertising's core API was designed in the mid-2000s when SOAP was the enterprise web services standard, and it remains SOAP-based for backward compatibility with the thousands of agencies and tools that integrate with it. Microsoft has added REST endpoints for authentication and some reporting functions, but the bulk of campaign management and complex reporting still uses SOAP. The bing-ads npm package wraps the SOAP complexity in JavaScript promises.
How long do Microsoft Advertising refresh tokens last?
Microsoft Advertising refresh tokens have a 90-day rolling expiry — each time the refresh token is used to obtain a new access token, the 90-day clock resets. A dashboard that runs daily requests will effectively never expire. If the application is inactive for 90 consecutive days, the refresh token expires and users must re-authorize through the OAuth flow. Monitoring the token health and alerting before expiry prevents dashboard outages.
What is the difference between Customer ID and Account ID in Microsoft Advertising?
In Microsoft Advertising, a Customer is the top-level entity (typically a company or agency), and an Account is a billing account under that customer with its own budget and campaigns. Customer ID identifies the company, while Account ID identifies the specific ad account. Most API calls require both: Customer ID in the CustomerId header and Account ID in the CustomerAccountId header. Get both from ads.microsoft.com — Customer ID appears in the top-right corner and Account ID appears in the account settings.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation