URL: /code/integrations/microsoft-ads · Id: `page:/code/integrations/microsoft-ads`

# Microsoft Ads

> Your agent copies the Google campaign that already works to Bing, signed in as you, and reads what Bing searchers click and buy.

Your agent copies the Google campaign that already works to Bing, signed in as you, and reads what Bing searchers click and buy.

```text title="Run it · Ask"
Copy my Google exact-match rent campaign into Microsoft Ads at $10 a day, and show me what it would bring over.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Campaign.create({ venue: 'bing', copyFrom: 'campaigns/google-rent-exact', budget: { amount: 10, currency: 'USD', per: 'day' } })
```

```json title="Run it · Response"
{
  "$type": "Campaign",
  "mode": "try",
  "stored": false,
  "venue": "bing",
  "copiedFrom": "google-ads",
  "adGroups": 4,
  "keywords": 14,
  "negatives": 22,
  "ads": 8,
  "budget": {
    "amount": 10,
    "currency": "USD",
    "per": "day"
  },
  "runsBy": "browser",
  "steps": 18
}
```

- **Connect:** Sign in once, 3 grants
- **Runs through:** [Your account or a browser agent](/code/integrations#three-ways)
- **First run:** Forecast Bing clicks and cost for your 10 best exact terms, about 90¢
- **Sites:** [Bing](/venues#bing)

## Connect once
1. Call `Integration.connect` with `system: 'ads'` and `venue: 'bing'`, then open the `connectUrl` it returns and sign in as the Microsoft account that holds your Microsoft Ads account.
2. Sign in once more in the browser window the platform opens; that saved session runs the import and every change.
3. Set the spend on your standing approval with `Mandate.authorize`; Microsoft bills your account, never the platform.

| Grant | What it unlocks | Scope |
| --- | --- | --- |
| Your Microsoft Ads account | Campaigns, ad groups with their bids, ads with their headlines and URLs, and keywords with their match types | `https://ads.microsoft.com/msads.manage` |
| Stay connected | Your agents keep reading without you signing in each day | `offline_access` |
| Your saved session | The Google import, new campaigns and every budget change, done in Microsoft Ads’ own screens | `browser session: ads.microsoft.com` |

| To | Do this |
| --- | --- |
| Pause | `Integration.pause` stops every call through Microsoft at once and keeps the token; `Integration.resume` picks up from the same cursor. |
| Reconnect | `Integration.reconnect` returns a fresh `connectUrl` that lasts one hour, and keeps the Integration's id and cursor, so nothing imports twice. |
| Disconnect | `Integration.revoke` revokes the token with Microsoft; every record it brought in stays in your records. |

## What your agents do
| Ask | Verb | Runs through | Price |
| --- | --- | --- | --- |
| Copy my Google exact-match campaign into Microsoft Ads at a fifth of its budget, signed in as me. | [`Campaign.create`](/code/apps/ads/create-campaign) | A browser agent | About 1.6¢ a step |
| Add the winning headline as a new ad, signed in as me. | [`Ad.create`](/code/apps/ads/create-ad) | A browser agent | About 1.6¢ a step |
| Raise the Bing budget to $15 a day, signed in as me. | [`Campaign.update`](/code/apps/ads/update-campaign) | A browser agent | About 1.6¢ a step |
| Show me my Bing campaigns, ad groups and keywords with their bids. | [`Metric.query`](/code/apps/analytics-and-experiments/query) | Your account | Free |

Calls on your own account are free; Microsoft bills your spend directly.

Reading this site’s public data needs no account: [`Keyword.forecast`](/code/data/keywords/forecast) is a Data call (the [three ways a call runs](/code/integrations#three-ways)).

## What it holds
| Noun | The account’s object | When | Which side leads |
| --- | --- | --- | --- |
| [Campaign](https://startupbuilder.dev/business/nouns/campaign) | `campaign` | Your agent imports or builds it | Your records |
| [Ad](https://startupbuilder.dev/business/nouns/ad) | `responsive search ad` | Your agent imports or builds it | Your records |
| [Keyword](https://startupbuilder.dev/business/nouns/keyword) | `keyword` | Your agent imports it | The account |

## Events and webhooks
| Direction | Event |
| --- | --- |
| Emits | `Campaign.created`, `Campaign.updated`, `Ad.created`, `Entry.posted` |

Any Event can wake a Workflow, and a Watch delivers it to your endpoint, signed ([Events and webhooks](/code/events)).

## Limits
| What | Limit | Call |
| --- | --- | --- |
| Ads per ad group | Three enabled responsive search ads; a fourth waits until one is paused | [`Ad.create`](/code/apps/ads/create-ad) |
| What the import copies | Campaigns, ad groups, keywords, ads and negatives as they stand in Google Ads; the agent then sets the budget your standing approval allows | [`Campaign.create`](/code/apps/ads/create-campaign) |
| Ad review | Microsoft reviews every ad; a disapproval comes back as `venue-rejected` with the reason | [`Ad.create`](/code/apps/ads/create-ad) |
| Spend | Your standing approval holds the cap; a budget change past it waits with `over-mandate` | [`Campaign.update`](/code/apps/ads/update-campaign) |
| A day’s spend | Microsoft may spend up to twice the daily budget on a busy day, and never more than the daily budget times 30.4 in a month; your standing approval’s cap is set on the month for that reason | [`Campaign.create`](/code/apps/ads/create-campaign) |

## Start here
| Move | The rule |
| --- | --- |
| Structure | Copy the Google campaign only once it has 500 clicks read and terms that convert: the same exact terms, ads and negatives, nothing new. |
| Budget | A fifth of the Google daily budget to start, moved up only on cost per signup. |
| Creative | The Google ads as they are; change only what Bing’s own search terms show is wrong. |
| Kill rule | The Google rule: at 500 clicks, stop if cost per signup at the interval’s upper bound fails break-even, and scale only when the lower bound passes. |

| Benchmark | Source |
| --- | --- |
| Start at a fifth of the Google budget | Our planning number |
| 500 clicks before a stop, hold or scale call; no signup in 150 clicks stops | Our calculation, the same rule as Google Ads |

## Field map
| Our field | Microsoft Ads’s field | Notes | Calls |
| --- | --- | --- | --- |
| `Campaign.objective` | `campaign goal` | Set in Microsoft Ads’ own screens; `leads` bids toward the signup conversion the import brings over | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign), [`Ad.create`](/code/apps/ads/create-ad) |
| `Campaign.budget` | `campaign daily budget` | None. | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign), [`Ad.create`](/code/apps/ads/create-ad) |
| `Campaign.state` | `campaign status` | `live` is Active, `paused` Paused | [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.keywords` | `keyword text and match type` | Exact unless the term says otherwise; phrase and broad on request | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Campaign.negatives` | `campaign negative keywords` | None. | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.bid` | `ad group default bid, or the bid strategy` | `lowest-cost` is maximize clicks; a `costPer` cap is a manual CPC bid | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.targeting` | `location targeting` | Countries and regions; Microsoft also shows ads to people searching about a place unless you set people in it | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Campaign.startsAt` | `ad group start date` | None. | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Ad.headline` | `responsive search ad headlines` | Up to 15, each 30 characters | [`Ad.create`](/code/apps/ads/create-ad) |
| `Ad.body` | `responsive search ad descriptions` | Up to 4, each 90 characters | [`Ad.create`](/code/apps/ads/create-ad) |
| `Ad.landingUrl` | `final URL` | None. | [`Ad.create`](/code/apps/ads/create-ad) |
| `Ad.path` | `display URL path 1 and path 2` | 15 characters each, shown after your domain | [`Ad.create`](/code/apps/ads/create-ad) |

## What try does here
Returns what the import would bring over, term by term with the budget it would set, and the steps the browser agent would take; no session opens and nothing changes.

## How long a connection lasts
A connection lasts 90 days from its last use: Microsoft replaces the refresh token on every use, and your agents use it daily.

The platform asks you to reconnect 7 days before it runs out; until you do, a call that needs it refuses with [`needs-reauth`](/code/refusals#needs-reauth).

## Formats
| Site | Format | Size | Limits |
| --- | --- | --- | --- |
| [Bing](/venues#bing) | Responsive search ad | None. | Up to 15 headlines of 30 characters and 4 descriptions of 90; three enabled per ad group |

## Plays and strategies
| Kind | Where |
| --- | --- |
| Plays | [Paid search](/plays/paid-search), [Promote winners](/plays/promote-winners) |
| Strategies | [Paid](/strategy/paid) |

## Used by
No Workflow, agent task or Service acts through it yet: your agents call it directly.

## Reference

| Call | Path | Price |
| --- | --- | --- |
| [`Campaign.create`](/code/apps/ads/create-campaign) | `POST /campaigns/create` | About 1.6¢ a step |
| [`Ad.create`](/code/apps/ads/create-ad) | `POST /ads/create` | About 1.6¢ a step |
| [`Campaign.update`](/code/apps/ads/update-campaign) | `POST /campaigns/update` | About 1.6¢ a step |
| [`Metric.query`](/code/apps/analytics-and-experiments/query) | `POST /metrics/query` | Free |