# Ads

Create, boost and measure campaigns on nine ad networks with one set of endpoints, from Meta and Google to LinkedIn, TikTok, ChatGPT and Whop.

Pozy runs paid campaigns on nine ad networks through one set of `/v1/ads` endpoints. You connect an ad account once, then create campaigns, boost posts, build audiences, send conversions and read spend with the same request shapes everywhere. Each network keeps its own rules, and the pages in this section say where they differ.

## Supported networks

| Network | Account platform | `platform` on ad endpoints | Connect with |
| --- | --- | --- | --- |
| [Meta Ads](https://pozy.ai/docs/ads/meta-ads) (Facebook, Instagram, Messenger, Threads, WhatsApp placements) | `metaads` | `facebook` or `instagram` | OAuth, classic or business login |
| [Google Ads](https://pozy.ai/docs/ads/google-ads) (Search, Display, Performance Max, Demand Gen) | `googleads` | `google` | OAuth |
| [TikTok Ads](https://pozy.ai/docs/ads/tiktok-ads) | `tiktokads` | `tiktok` | OAuth |
| [LinkedIn Ads](https://pozy.ai/docs/ads/linkedin-ads) | `linkedinads` | `linkedin` | OAuth, reuses the LinkedIn token when one is connected |
| [Pinterest Ads](https://pozy.ai/docs/ads/pinterest-ads) | `pinterestads` | `pinterest` | OAuth, reuses the Pinterest token |
| [X Ads](https://pozy.ai/docs/ads/x-ads) | `xads` | `twitter` | OAuth, created from a connected X account |
| [Reddit Ads](https://pozy.ai/docs/ads/reddit-ads) | `redditads` | `reddit` | OAuth |
| [ChatGPT Ads](https://pozy.ai/docs/ads/chatgpt-ads) | `openaiads` | `openai` | API key from ChatGPT Ads Manager |

An ad connection is its own connected account with its own `accountId`. It sits in a [profile](https://pozy.ai/docs/build/profiles) next to the posting accounts, and it counts once toward [pricing](https://pozy.ai/docs/billing) however many ad accounts it can reach.

> **Reddit:**
> 
> Organic Reddit publishing is paused, but Reddit Ads is a separate connection and works normally.

## How the pieces fit

Every network nests ads in three levels. Pozy uses the same three names for all of them, and maps each one to the network's own object.

| Pozy level | Id field on an ad | Meta | Google | TikTok | LinkedIn | X |
| --- | --- | --- | --- | --- | --- | --- |
| Campaign | `platformCampaignId` | Campaign | Campaign | Campaign | Campaign group | Campaign |
| Ad set | `platformAdSetId` (`adSetId`) | Ad set | Ad group | Ad group | Campaign | Line item |
| Ad | `platformAdId`, plus Pozy's `_id` | Ad | Ad (or asset group on Performance Max) | Ad | Creative | Promoted post |

A few more objects show up around them:

- **Ad account** (`adAccountId`): the account the network bills, such as `act_1234567890` on Meta or a customer id on Google. One connection can reach many.
- **Creative**: the headline, body, media, call to action and link. On Meta you can build one on its own and reuse it with `existingCreativeId`.
- **Audience**: a list of people you upload or a segment the network builds for you. See [Audiences](https://pozy.ai/docs/ads/audiences).

`POST /v1/ads/create` builds the whole tree in one call. `POST /v1/ads/boost` wraps an organic post in the same tree. `GET /v1/ads/tree` reads it back with metrics rolled up per level.

## Connect an ad account

### Start the connection

Call `GET /v1/connect/{platform}/ads` with the `profileId`. `platform` is one of `facebook`, `instagram`, `linkedin`, `tiktok`, `twitter`, `pinterest`, `googleads` or `redditads`. ChatGPT Ads connects with an API key instead, through `POST /v1/connect/openai-ads/credentials`.

```bash cURL
curl "https://api.pozy.ai/v1/connect/googleads/ads?profileId=66b2e19d8c3f5a7e9d0b1c2a&redirect_url=https://app.example.com/ads/connected" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/connect/googleads/ads?profileId=66b2e19d8c3f5a7e9d0b1c2a&redirect_url=https:%2F%2Fapp.example.com%2Fads%2Fconnected", {
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// data.authUrl the first time, data.alreadyConnected + data.accountId afterwards
```
```python Python
import os, requests

resp = requests.get(
    "https://api.pozy.ai/v1/connect/googleads/ads",
    headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
    params={"profileId": "66b2e19d8c3f5a7e9d0b1c2a", "redirect_url": "https://app.example.com/ads/connected"},
)
print(resp.json())
```

### Send the user to the consent screen

The first time, the response carries an `authUrl`. Open it in a browser. When the user finishes, they land on your `redirect_url`. LinkedIn and Pinterest skip this step when the profile already has a posting account with the ads permissions.

### Read the connection

Call the same endpoint again. It now answers `alreadyConnected: true` with the `accountId` of the ad connection. Branch on that field rather than on the URL.

### Pick an ad account

Call `GET /v1/ads/accounts?accountId=...` to list the ad accounts the connection reaches. Each `accounts[].id` is the `adAccountId` every other ads call takes.

`adAccountId` or `adAccountIds` on the connect call limit the sync to the ad accounts you name. The full flow, with hosted and headless options, is in [Connecting accounts](https://pozy.ai/docs/connect).

## What each network supports

| Capability | Meta | Google | TikTok | LinkedIn | Pinterest | X | Reddit | ChatGPT |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| [Create a campaign](https://pozy.ai/docs/ads/meta-ads) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| [Boost an existing post](https://pozy.ai/docs/ads/boost-posts) | Yes | – | Yes (Spark Ads) | Yes | Yes | Yes | Yes | – |
| Pause, resume, change budget | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Edit targeting after launch | Yes | Campaign-level | Yes | Yes | – | – | Yes | – |
| Swap the creative on a live ad | Yes | Search text | Yes | New media | – | – | – | – |
| Duplicate a campaign | Yes | – | Yes | Yes | – | – | – | – |
| [Targeting search](https://pozy.ai/docs/ads/targeting) | Yes | Yes | Yes | Yes | Interests | Yes | Yes | – (countries only) |
| Reach estimate or forecast | Yes | – | – | Yes | Yes | Yes | – | – |
| [Audiences](https://pozy.ai/docs/ads/audiences) | Yes | Customer Match | Yes | Yes | Yes | Yes | Target only | – |
| [Conversions API](https://pozy.ai/docs/ads/conversions) | Yes | Yes | Yes | Yes | Yes | – | Yes | Yes |
| [Pixels and tags](https://pozy.ai/docs/ads/pixels) | Yes | Google tag | Yes | Insight Tag | Yes | Yes | – | Yes |
| [Lead forms](https://pozy.ai/docs/ads/lead-forms) | Yes | – | – | Yes | – | – | – | – |
| [Product catalogs](https://pozy.ai/docs/ads/catalogs) | Yes | – | – | – | – | – | – | – |
| [Automated rules](https://pozy.ai/docs/ads/rules) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | – |
| [Reporting](https://pozy.ai/docs/ads/reporting) | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Demographic breakdowns | Per ad and campaign | – | Per ad | Firmographics | – | – | – | – |

A field a network cannot honor is rejected at create time with a `400` that names it. Pozy never drops a field quietly.

## Money and units

- Budgets and bids are whole units of the ad account's currency: `75` on a USD account is $75.00. Never cents, never micros.
- Each campaign node reports its `currency`. Pozy never converts between currencies, so do not add spend across ad accounts that bill differently.
- Raw reports from a network (Google GAQL rows, Keyword Planner, Meta live insights) keep that network's units. Google's micros, for example, divide by 1,000,000.

## Reviews and statuses

Every network reviews ads before they deliver. A successful create means the ad exists, not that it runs. Each ad carries three separate fields:

| Field | Answers | Values |
| --- | --- | --- |
| `status` | Is it delivering? | `active`, `paused`, `pending_review`, `rejected`, `completed`, `cancelled`, `error` |
| `configuredStatus` | What did you set on this ad? | `ACTIVE`, `PAUSED` |
| `reviewStatus` | What did the network's review decide? | `in_review`, `approved`, `rejected`, `with_issues` |

`status` inherits from parents: an active ad under a paused campaign reads `paused`. Deleted ads stay in the tree as `cancelled`, so their past spend keeps counting.

## Retries

Ad creates and boosts are not idempotent on the networks' side. A blind retry after a timeout can launch a second campaign that spends. Send an `Idempotency-Key` header on every create, boost and duplicate, and reuse it on retries. See [Idempotency](https://pozy.ai/docs/getting-started/idempotency).

## Pricing

Managed ads bill per active ad per month. The first 500 active ads in a workspace are free, then each costs $0.01 per month. Only ads that are running or in review count: paused, ended, rejected and deleted ads do not, and the count is metered daily, so pausing a campaign takes its ads off the meter the same day. There is no percentage of ad spend. The spend itself is billed by the network to the ad account's own payment method.

| Active ads | Monthly cost |
| --- | --- |
| 400 | Free |
| 1,000 | $5 |
| 5,000 | $45 |
| 50,000 | $495 |

See [Pricing](https://pozy.ai/docs/billing) for every other meter and how they roll up on one invoice.

## Webhook events

Subscribe to these to react to reviews, delivery problems, budgets and new leads without polling. Payloads are in the [events reference](https://pozy.ai/docs/webhooks/events).

| Event | When it fires |
| --- | --- |
| [`account.ads.initial_sync_completed`](https://pozy.ai/docs/webhooks/events#account-ads-initial-sync-completed) | Ads initial sync completed. |
| [`account.ads.sync_failed`](https://pozy.ai/docs/webhooks/events#account-ads-sync-failed) | Ads sync failed. |
| [`account.ads.sync_recovered`](https://pozy.ai/docs/webhooks/events#account-ads-sync-recovered) | Ads sync recovered. |
| [`lead.received`](https://pozy.ai/docs/webhooks/events#lead-received) | Lead received. |
| [`ad.status_changed`](https://pozy.ai/docs/webhooks/events#ad-status-changed) | Ad status changed. |
| [`ad.review.approved`](https://pozy.ai/docs/webhooks/events#ad-review-approved) | Ad review approved. |
| [`ad.review.rejected`](https://pozy.ai/docs/webhooks/events#ad-review-rejected) | Ad review rejected. |
| [`ad.delivery_issue`](https://pozy.ai/docs/webhooks/events#ad-delivery-issue) | Ad delivery issue. |
| [`campaign.budget.depleted`](https://pozy.ai/docs/webhooks/events#campaign-budget-depleted) | Campaign budget depleted. |
| [`ad_account.status_changed`](https://pozy.ai/docs/webhooks/events#ad-account-status-changed) | Ad account status changed. |
| [`ad.video.processed`](https://pozy.ai/docs/webhooks/events#ad-video-processed) | Ad video processed. |
| [`ad_rule.triggered`](https://pozy.ai/docs/webhooks/events#ad-rule-triggered) | Ad rule triggered. |

## Endpoints

The ads surface is large. These are the calls most integrations start with; each network page lists its own.

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/connect/{platform}/ads`](https://pozy.ai/docs/api/connectAds) | Connect ads for a platform |
| [`GET /v1/ads/accounts`](https://pozy.ai/docs/api/listAdAccounts) | List ad accounts |
| [`POST /v1/ads/create`](https://pozy.ai/docs/api/createStandaloneAd) | Create standalone ad |
| [`POST /v1/ads/boost`](https://pozy.ai/docs/api/boostPost) | Boost post as ad |
| [`GET /v1/ads/tree`](https://pozy.ai/docs/api/getAdTree) | Get campaign tree |
| [`GET /v1/ads`](https://pozy.ai/docs/api/listAds) | List ads |
| [`GET /v1/ads/{adId}`](https://pozy.ai/docs/api/getAd) | Get ad details |
| [`PUT /v1/ads/{adId}`](https://pozy.ai/docs/api/updateAd) | Update ad |
| [`PUT /v1/ads/{adId}/status`](https://pozy.ai/docs/api/updateAdStatus) | Pause or resume a single ad |
| [`PUT /v1/ads/campaigns/{campaignId}/status`](https://pozy.ai/docs/api/updateAdCampaignStatus) | Pause or resume a campaign |
| [`GET /v1/ads/{adId}/analytics`](https://pozy.ai/docs/api/getAdAnalytics) | Get ad analytics |
| [`GET /v1/ads/timeline`](https://pozy.ai/docs/api/getAdsTimeline) | Get daily account metrics |
| [`GET /v1/ads/targeting/search`](https://pozy.ai/docs/api/searchAdTargeting) | Search targeting options |
| [`POST /v1/ads/audiences`](https://pozy.ai/docs/api/createAdAudience) | Create custom audience |

The full list, grouped by tag, is in the [API reference](https://pozy.ai/docs/api#ad-campaigns).
