Ads
Target ads by location, age, interests, keywords, job titles or subreddits, look up each network's ids with one search endpoint and estimate reach first.
Targeting tells a network who should see an ad. Pozy uses one set of field names across networks, then translates them into each network's own targeting spec. Portable fields such as countries, ageMin, ageMax, gender and languages work everywhere. The rest only mean something on the network they were looked up for, and a field a network cannot honor is rejected with a 400 that names it.
#Where targeting goes
| Call | Shape |
|---|---|
POST /v1/ads/create |
Flat fields at the top level (Meta, TikTok, Pinterest, X, Reddit), or a nested targeting object (LinkedIn, Google accepts both) |
POST /v1/ads/boost |
Inside targeting |
POST /v1/ads/messaging, POST /v1/ads/call |
Flat fields, like create |
PUT /v1/ads/{adId} |
targeting updates the ad's ad set or campaign where the network allows it |
PUT /v1/ads/campaigns/{campaignId}/targeting |
Google campaign-level devices, locations and languages |
POST /v1/ads/targeting/reach-estimate |
Inside spec |
#Look up ids
Cities, regions, interests, job titles and subreddits are ids that belong to each network. Find them with GET /v1/ads/targeting/search, using the accountId of the ad connection you will create the ad on.
curl -G https://api.pozy.ai/v1/ads/targeting/search \
-H "Authorization: Bearer $POZY_API_KEY" \
--data-urlencode "accountId=66b2e19d8c3f5a7e9d0b1c2d" \
--data-urlencode "q=Amsterdam" \
--data-urlencode "dimension=geo" \
--data-urlencode "geoType=city" \
--data-urlencode "countryCode=NL"const res = await fetch("https://api.pozy.ai/v1/ads/targeting/search?accountId=66b2e19d8c3f5a7e9d0b1c2d&q=Amsterdam&dimension=geo&geoType=city&countryCode=NL", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
const cityKey = data.results[0].id; // send as { key: cityKey }import os, requests
resp = requests.get(
"https://api.pozy.ai/v1/ads/targeting/search",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={"accountId": "66b2e19d8c3f5a7e9d0b1c2d", "q": "Amsterdam", "dimension": "geo", "geoType": "city", "countryCode": "NL"},
)
print(resp.json()["results"][0]){
"results": [
{ "id": "2759794", "name": "Amsterdam", "type": "city", "path": ["Netherlands", "North Holland", "Amsterdam"], "audienceSize": null }
]
}Send a geo result's id as key in cities, regions, zips or metros. A country result's id is the ISO code for countries. Interest and behavior results go in interests and behaviors as { id, name }.
dimension |
Meta | TikTok | X | |||
|---|---|---|---|---|---|---|
geo |
Yes | Yes | Yes | Yes | Yes | Yes |
interest |
Yes | Yes (affinity and in-market) | Yes | Yes | Yes | Yes |
behavior |
Yes | – | Yes | Yes | – | – |
language |
– | Yes | – | – | – | – |
income |
Yes | Yes (top_10 only) |
– | – | – | – |
workPosition, workEmployer, workIndustry |
Yes | – | – | – | – | – |
industry, jobFunction, seniority, companySize, skill |
– | – | – | Yes | – | – |
community, conversationKeyword |
– | – | – | – | – | Yes |
geoType narrows geo searches to country, region, city (the default), subcity, neighborhood, place, zip, metro_area or geo_market. Google and LinkedIn ignore it and return every level, so filter on each result's type. GET /v1/ads/targeting/browse walks Meta's targeting category tree (by type and parentNodeId) instead of searching it. dimension defaults to interest. A dimension the network does not support returns 400 or 404.
#Estimate reach first
POST /v1/ads/targeting/reach-estimate returns an audience size range before you create anything:
{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"adAccountId": "act_1234567890",
"spec": { "countries": ["NL"], "ageMin": 25, "ageMax": 54, "interests": [{ "id": "6003107902433", "name": "Interior design" }] }
}{ "available": true, "lower": 412000, "upper": 585000, "estimateReady": true }estimateReady: false means the network is still computing, which happens with brand-new audiences: retry shortly. Networks without an estimate return available: false. On LinkedIn, bid pricing and supply forecasts add bid ranges and projected delivery.
#Meta
| Field | Values |
|---|---|
countries, countryGroups |
ISO codes, or groups such as eea or north_america. Defaults to ["US"] only when no geo field is sent. |
cities |
{ key, radius, distance_unit }. Meta's minimum radius is about 17 km (10 miles); smaller resolves to an empty audience. |
regions, zips, metros |
{ key } from the search, for example US:94304 or DMA:807 |
customLocations |
{ latitude, longitude, radius, distanceUnit } |
excludedLocations |
Countries, regions, cities, ZIPs, places, neighborhoods, custom locations |
ageMin, ageMax |
13 to 65 |
gender |
all, male, female |
interests, behaviors |
{ id, name } |
workPositions, workEmployers, workIndustries |
{ id, name } |
incomeTier |
top_5, top_10, top_10_25, top_25_50 (household income by ZIP) |
languages |
en targets every variant, en_GB one |
userOs, userDevice |
For example iOS_ver_14.0_and_above, iPhone |
audienceInclude, audienceExclude |
Audience ids (Audiences) |
placements |
Manual placements. Omit for automatic placements. |
savedTargetingId |
A saved targeting audience as the base, with inline fields on top |
rawTargeting |
A Meta-native spec for anything the flat fields do not cover, such as layered flexible_spec |
advantageAudience |
0 keeps targeting strict, 1 lets Meta expand |
Do not send a city together with the country that contains it. Meta rejects the ad. Drop the country, or use a different one.
Housing, employment and credit ads (specialAdCategories) cannot use income or ZIP targeting. EU delivery needs dsaBeneficiary and dsaPayor.
| Field | Values |
|---|---|
countries |
ISO codes. Search and Display default to US with no geo; Performance Max and Demand Gen run worldwide. |
regions, cities, zips, metros |
{ key }, Google's geo target ids |
customLocations |
A pin with radius (Google has no city radius) |
excludedLocations |
Countries, regions, cities, ZIPs |
languages |
ISO 639-1 codes plus variants such as zh_CN |
ageMin, ageMax, gender |
Search and Display only, 18 and over |
incomeTier |
top_10 only |
interests |
Affinity and in-market segments, Search and Display |
audienceInclude, audienceExclude |
User list ids, such as Customer Match lists |
keywords, negativeKeywords |
Search only, see Google Ads |
locationTargetingType sets whether locations reach people physically there (presence) or also people interested in them (presence_or_interest, Google's default). Performance Max and Demand Gen take geo and language only. On a running campaign, PUT /v1/ads/campaigns/{campaignId}/targeting replaces devices (MOBILE, DESKTOP, TABLET, CONNECTED_TV, each with an optional bid modifier), locations, excludedLocations and languages.
#TikTok
| Field | Values |
|---|---|
countries |
ISO codes. Required on boosts. |
regions, cities, zips, metros |
Create only |
ageMin, ageMax, gender, languages |
zh_TW targets traditional Chinese |
interests |
From the interest search |
behaviors |
video:<id> or creator:<id>. Restricts delivery to the TikTok placement. |
incomeTier |
As on Meta |
audienceInclude, audienceExclude |
Processed custom audiences |
PUT /v1/ads/{adId} with targeting is a partial update; fields you leave out keep their value, except that a new location replaces both location and ZIP targeting.
| Field | Values |
|---|---|
countries, regions |
ISO codes or urn:li:geo:*. At least one location; none means the United States. |
industries, seniorities, jobFunctions, jobTitles, skills |
Id fragments or full URNs |
companySizes |
A to I staff-count ranges |
interests, behaviors |
URNs from the search |
audienceInclude, audienceExclude |
Matched audience segment ids |
ageMin, ageMax, gender, languages |
Rounded to LinkedIn's buckets |
excludedLocations.countries |
ISO codes |
jobTitles cannot be combined with seniorities or jobFunctions. Cities, ZIP codes, radius and income return 400. Updates through PUT /v1/ads/{adId} merge facet by facet; send a facet empty to remove it.
countries, regions, metros, zips, ageMin, ageMax, gender, languages, interests (from GET /v1/ads/interests) and customer list audiences. regions and zips cannot be combined. Targeting cannot change after create: recreate the ad.
#X
countries, regions, metros, cities, zips, interests, Tailored Audiences, gender and languages. Age rounds out to X's fixed ranges (starting at 13, 18, 21, 25, 35 or 50 and ending at 24, 34, 49 or 54, or open-ended), so ageMin: 20, ageMax: 40 targets 18 to 49. Income, custom locations and behaviors are rejected. Targeting cannot change after create.
| Field | Values |
|---|---|
countries, regions, metros, cities, zips |
No location means US |
excludedLocations |
Countries, regions, cities and ZIPs |
gender, languages |
|
targeting.interests |
From the interest search |
targeting.communities, targeting.excludedCommunities |
Subreddit names, with or without r/ |
targeting.conversationKeywords, targeting.excludedConversationKeywords |
Free text |
audienceInclude, audienceExclude |
Custom audiences built in Reddit Ads Manager |
Age, income and radius are not available. Targeting can be changed after launch with PUT /v1/ads/{adId}.
#ChatGPT
ChatGPT ads target by country only.
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/ads/interests |
Search targeting interests |
GET /v1/ads/targeting/search |
Search targeting options |
GET /v1/ads/targeting/browse |
Browse targeting categories |
POST /v1/ads/targeting/reach-estimate |
Estimate audience reach |
POST /v1/ads/targeting/bid-pricing |
Suggested bid and budget bounds |
POST /v1/ads/targeting/supply-forecast |
Forecast ad delivery |