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

# X Ads

> Your agent promotes the X post that already earned replies to people who follow the accounts your buyers follow, signed in as you.

Your agent promotes the X post that already earned replies to people who follow the accounts your buyers follow, signed in as you.

```text title="Run it · Ask"
Promote my late-rent thread for $100 over 5 days to followers of the 8 accounts property managers follow.
```

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

const result = await $.try.Campaign.create({ venue: 'x', objective: 'engagement', promote: 'posts/x-late-rent-thread', budget: { amount: 100, currency: 'USD', per: 'test' }, audience: { followersOf: 8 } })
```

```json title="Run it · Response"
{
  "$type": "Campaign",
  "mode": "try",
  "stored": false,
  "venue": "x",
  "promoting": "posts/x-late-rent-thread",
  "organic": {
    "replies": 14,
    "impressions": 3200
  },
  "budget": {
    "amount": 100,
    "currency": "USD",
    "days": 5
  },
  "readAt": "5,000 impressions",
  "runsBy": "browser",
  "steps": 14
}
```

- **Connect:** Sign in once, 1 grant
- **Runs through:** [A browser agent](/code/integrations#three-ways)
- **First run:** Find which of your posts earned the most replies, about 0.03¢
- **Sites:** [X](/venues#x)

## Connect once
1. Call `Integration.connect` with `system: 'ads'` and `venue: 'x'`; a browser window opens on X Ads.
2. Sign in with the X account you post from; the session is saved to your browser profile.
3. Set the spend on your standing approval with `Mandate.authorize`; X bills your ad account, never the platform.

| Grant | What it unlocks | Scope |
| --- | --- | --- |
| Your saved X Ads session | Promoting a post, its audience, budget and results, in X’s own screens | `browser session: ads.x.com` |

| To | Do this |
| --- | --- |
| Pause | `Integration.pause` holds every run that needs your X Ads session; the session stays saved. |
| Reconnect | `Integration.reconnect` opens a browser window where you sign in to X Ads once more; the saved session replaces the old one. |
| Disconnect | `Integration.revoke` deletes the saved X Ads session; every Post and reply it made stays in your records. |

## What your agents do
| Ask | Verb | Runs through | Price |
| --- | --- | --- | --- |
| Promote that post for $100 to followers of these 8 accounts, signed in as me. | [`Campaign.create`](/code/apps/ads/create-campaign) | A browser agent | About 1.6¢ a step |
| Stop the promotion, signed in as me. | [`Campaign.update`](/code/apps/ads/update-campaign) | A browser agent | About 1.6¢ a step |
| Show me impressions, replies and link clicks on the promoted post, signed in as me. | [`Metric.query`](/code/apps/analytics-and-experiments/query) | A browser agent | About 1.6¢ a step |

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

Reading this site’s public data needs no account: [`Keyword.search`](/code/data/keywords/search) 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 and ad group` | Your agent promotes a post | Your records |
| [Ad](https://startupbuilder.dev/business/nouns/ad) | `promoted post` | Your agent promotes it; X’s review sets its status | Your records |
| [Message](https://startupbuilder.dev/business/nouns/message) | `reply` | Someone replies | The account |

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

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

## Limits
| What | Limit | Call |
| --- | --- | --- |
| Ad review | X reviews every promoted post; a rejection comes back as `venue-rejected` with the reason | [`Campaign.create`](/code/apps/ads/create-campaign) |
| What gets promoted | A post already on your account, as it is; the agent never writes a post only for the ad | [`Campaign.create`](/code/apps/ads/create-campaign) |
| Spend | Your standing approval holds the cap; a change past it waits with `over-mandate` | [`Campaign.update`](/code/apps/ads/update-campaign) |
| Post length | 280 weighted characters on a standard account, a link counting as 23 wherever it points | [`Campaign.create`](/code/apps/ads/create-campaign) |

## Start here
| Move | The rule |
| --- | --- |
| Structure | One campaign promoting one post that earned replies on its own, aimed at followers of 5 to 10 accounts your buyers follow. |
| Budget | $100 for the first test over 5 days, and only on a post that already started conversations. |
| Creative | The post as it earned its replies; if it carries no link, your agent adds the link in the first reply, not in an edit. |
| Kill rule | At 5,000 impressions, stop if the promoted post earns fewer replies per 1,000 impressions than it did on its own. |

| Benchmark | Source |
| --- | --- |
| Promote only a post with 3 or more replies from buyers | Our planning number |
| Judge at 5,000 impressions | Our planning number |
| A link counts as 23 of the 280 characters | X developer docs |

## Field map
| Our field | X Ads’s field | Notes | Calls |
| --- | --- | --- | --- |
| `Campaign.objective` | `campaign objective` | Picked in X Ads: `traffic` is website traffic; `engagement` is post engagement | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.targeting` | `follower look-alikes` | Of the accounts you name | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Campaign.budget` | `campaign total and daily budget` | None. | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.startsAt` | `ad group start time` | None. | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Campaign.endsAt` | `ad group end time` | None. | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.state` | `campaign status` | `live` is active, `paused` paused | [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.bid` | `ad group bid strategy` | `lowest-cost` is autobid; a `costPer` cap is a target cost | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.keywords` | `keyword targeting` | Reaches people who searched or posted the words lately, beside the follower look-alikes | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Ad.landingUrl` | `website card URL` | Added in the first reply when the post carries no link | [`Campaign.create`](/code/apps/ads/create-campaign) |

## What try does here
Returns the promotion as X Ads will hold it, with the steps the browser agent would take; no session opens and nothing is promoted.

## How long a connection lasts
A connection lasts as long as the site keeps your saved session signed in; the platform checks the session before every run.

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 |
| --- | --- | --- | --- |
| [X](/venues#x) | Standard post | None. | 280 weighted characters; a link counts as 23 and an emoji as 2 |
| [X](/venues#x) | Promoted post | None. | A post already on your account, promoted as it is |

## Plays and strategies
| Kind | Where |
| --- | --- |
| Plays | [Paid social](/plays/paid-social), [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 |
| [`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` | About 1.6¢ a step |