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 (Facebook, Instagram, Messenger, Threads, WhatsApp placements) | metaads |
facebook or instagram |
OAuth, classic or business login |
| Google Ads (Search, Display, Performance Max, Demand Gen) | googleads |
google |
OAuth |
| TikTok Ads | tiktokads |
tiktok |
OAuth |
| LinkedIn Ads | linkedinads |
linkedin |
OAuth, reuses the LinkedIn token when one is connected |
| Pinterest Ads | pinterestads |
pinterest |
OAuth, reuses the Pinterest token |
| X Ads | xads |
twitter |
OAuth, created from a connected X account |
| Reddit Ads | redditads |
reddit |
OAuth |
| 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 next to the posting accounts, and it counts once toward pricing however many ad accounts it can reach.
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 | TikTok | 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 asact_1234567890on 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.
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.
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"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 afterwardsimport 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.
#What each network supports
| Capability | Meta | TikTok | X | ChatGPT | ||||
|---|---|---|---|---|---|---|---|---|
| Create a campaign | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Boost an existing post | 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 | Yes | Yes | Yes | Yes | Interests | Yes | Yes | – (countries only) |
| Reach estimate or forecast | Yes | – | – | Yes | Yes | Yes | – | – |
| Audiences | Yes | Customer Match | Yes | Yes | Yes | Yes | Target only | – |
| Conversions API | Yes | Yes | Yes | Yes | Yes | – | Yes | Yes |
| Pixels and tags | Yes | Google tag | Yes | Insight Tag | Yes | Yes | – | Yes |
| Lead forms | Yes | – | – | Yes | – | – | – | – |
| Product catalogs | Yes | – | – | – | – | – | – | – |
| Automated rules | Yes | Yes | Yes | Yes | Yes | Yes | Yes | – |
| 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:
75on 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.
#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 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.
| Event | When it fires |
|---|---|
account.ads.initial_sync_completed |
Ads initial sync completed. |
account.ads.sync_failed |
Ads sync failed. |
account.ads.sync_recovered |
Ads sync recovered. |
lead.received |
Lead received. |
ad.status_changed |
Ad status changed. |
ad.review.approved |
Ad review approved. |
ad.review.rejected |
Ad review rejected. |
ad.delivery_issue |
Ad delivery issue. |
campaign.budget.depleted |
Campaign budget depleted. |
ad_account.status_changed |
Ad account status changed. |
ad.video.processed |
Ad video processed. |
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 |
Connect ads for a platform |
GET /v1/ads/accounts |
List ad accounts |
POST /v1/ads/create |
Create standalone ad |
POST /v1/ads/boost |
Boost post as ad |
GET /v1/ads/tree |
Get campaign tree |
GET /v1/ads |
List ads |
GET /v1/ads/{adId} |
Get ad details |
PUT /v1/ads/{adId} |
Update ad |
PUT /v1/ads/{adId}/status |
Pause or resume a single ad |
PUT /v1/ads/campaigns/{campaignId}/status |
Pause or resume a campaign |
GET /v1/ads/{adId}/analytics |
Get ad analytics |
GET /v1/ads/timeline |
Get daily account metrics |
GET /v1/ads/targeting/search |
Search targeting options |
POST /v1/ads/audiences |
Create custom audience |
The full list, grouped by tag, is in the API reference.