Microsoft Advertising
Microsoft Advertising (the Bing Ads API, version 13) spreads its REST endpoints over several hosts, and a Permaura connector pins exactly one, so the catalog carries two connectors for it:
- Microsoft Advertising reaches
https://campaign.api.bingads.microsoft.com/CampaignManagement/v13: campaigns, ad groups, ads, keywords, budgets, targeting and conversion goals, with every create and update held behind an approval. - Microsoft Advertising Reporting reaches
https://reporting.api.bingads.microsoft.com/Reporting/v13: it requests a performance report and polls for the download link. Both of its actions are reads.
They take the same app, the same developer token and the same account details, so set those up once and make two connections. An agent that only answers performance questions needs only the second.
What you need
1. A Microsoft identity app. Register an application in the Microsoft Entra admin center (or the Azure portal) under App registrations, with Accounts in any organizational directory and personal Microsoft accounts as the supported account type, since Microsoft Advertising users sign in with either. Under Authentication add a Web platform whose redirect URI is your gateway's address followed by /oauth/callback: http://127.0.0.1:7376/oauth/callback on your own machine, https://<your-gateway-host>/oauth/callback otherwise. Under Certificates & secrets create a client secret and copy its value (not its id) straight away; it is shown once. The application (client) id is on the overview page. No API permission needs adding by hand: the gateway asks for https://ads.microsoft.com/msads.manage and offline_access at sign-in, and the consent page grants them.
2. A developer token. Sign in to Microsoft Advertising as a Super Admin and open the developer settings (ads.microsoft.com/cc/Settings/DevSettings, Request token). A universal token works for every user who signs in through your app. Microsoft issues it without a review step; the sandbox has its own universal token, BBD37VB98.
3. The customer id and account id. Open the Campaigns tab in Microsoft Advertising and read the URL: cid= is the customer id (the manager account) and aid= is the account id (the ad account). Not the eight-character account number shown in the UI; the API never takes that.
Add the connection
In the console, open Access, Connections, Add connection, pick Microsoft Advertising from the catalog, and fill in Developer token, Customer id and Account id. Under the credential choose OAuth, then Gateway-held · your own app, paste the application id and the client secret, check the redirect URI matches the one on your app registration, and choose Add. Microsoft's sign-in opens in a new tab; sign in as a user with access to the account and accept the consent page, and your browser lands on the gateway's "Connected" page once the tokens are sealed. Repeat with Microsoft Advertising Reporting for the second connection; the same app and values work.
Over the REST API, the three values travel as static_headers on the from-preset call:
BASE=http://127.0.0.1:7376
curl -s -X POST $BASE/v1/connections/from-preset \
-H @<(printf 'authorization: Bearer %s\n' "$TOKEN") -H 'content-type: application/json' -d '{
"preset_id": "microsoft-ads",
"name": "msads-acme",
"static_headers": { "DeveloperToken": "<your-developer-token>", "CustomerId": "<cid>", "CustomerAccountId": "<aid>" }
}'
Then start the authorization against the returned connection_id as on the Reddit page. The gateway refuses a from-preset call for either connector that leaves one of the three out.
The developer token is stored on the connection in the clear, beside the pinned host, for the reasons and with the trade-offs the Google Ads page sets out: it identifies your API access, the signed-in user's OAuth token is what authorizes the account, and that token is the part the gateway seals and refreshes. Access tokens from Microsoft last an hour; the gateway refreshes them two minutes before expiry with the refresh token offline_access provides.
What the agent can do
Microsoft Advertising:
| Action | What it does | On by default |
|---|---|---|
campaigns.list, campaigns.read | Lists an account's campaigns; reads campaigns by id | Yes |
ad_groups.list, ad_groups.read | Lists a campaign's ad groups; reads ad groups by id | Yes |
ads.list, ads.read | Lists an ad group's ads; reads ads by id | Yes |
keywords.list | Lists an ad group's keywords with bids and match types | Yes |
budgets.list | Lists the account's shared budgets | Yes |
campaign_criteria.list, ad_group_criteria.list | Lists targeting such as locations, ages and devices | Yes |
conversion_goals.list | Lists conversion goals | Yes |
campaigns.create, campaigns.update | Creates campaigns; pauses, activates or changes them and their budgets | No, and needs approval |
ad_groups.create, ad_groups.update | Creates ad groups; pauses, activates or changes them and their bids | No, and needs approval |
ads.create, ads.update | Creates ads; pauses, activates or changes them | No, and needs approval |
keywords.create, keywords.update | Adds keywords; changes their bids or status | No, and needs approval |
budgets.create, budgets.update | Creates or changes shared budgets | No, and needs approval |
Microsoft Advertising Reporting:
| Action | What it does | On by default |
|---|---|---|
reports.submit | Requests a report and returns its request id | Yes |
reports.poll | Polls the request; when its status is Success the answer carries the download URL | Yes |
Good to know
- Every call is a POST, reads included. The REST API mirrors its SOAP operations:
GetCampaignsByAccountIdisPOST /Campaigns/QueryByAccountIdwith the ids in a JSON body. Permaura's classifier would read those as writes, so the catalog marks everyQueryaction and both reporting actions as reads at medium risk. Updates arePUTon the collection (PUT /Campaigns), which the classifier gates on its own. - Body ids must match the headers.
AccountIdin a body must equal the connection'sCustomerAccountId; Microsoft answers a mismatch with an authorization error rather than an empty list. One connection reaches one ad account; a second account is a second connection. campaigns.listreturns Search campaigns unless told otherwise. PassCampaignTypeas a space-separated list, for example"Search Shopping Audience PerformanceMax", to see the rest.- Updates send whole objects.
campaigns.updatetakesCampaigns, a list of campaign objects each carrying itsIdand the fields to change; a field left null stays as it was. Pausing is{"Id": 123, "Status": "Paused"};"Active"resumes, and both ask for an approval. - Reports arrive as a file, not a response.
reports.submittakes aReportRequestwhoseTypenames the report, for instanceCampaignPerformanceReportRequestwithColumns,ScopeandTime. The answer is aReportRequestId;reports.pollreturnsPendinguntil the file is ready, thenSuccessand aReportDownloadUrlfor a zipped CSV. That URL is a signed link to Microsoft's download host, needs no credential, and lives a day, so the agent (or you) fetches it directly; the gateway's part ends at the link. - The sandbox is the same connector on another host. Choose Use a different host when you add the connection and enter
https://campaign.api.sandbox.bingads.microsoft.com/CampaignManagement/v13(or thereporting.api.sandbox.equivalent), with the sandbox developer token and a sandbox sign-in. Sandbox and production credentials do not cross. - Customer management is not covered. Finding a user's accounts through
SearchAccountslives on a third host (clientcenter.api.bingads.microsoft.com), and the ids are two numbers in a URL, so the catalog leaves that host out. Import a connector of your own under another id if you want it.