TikTok Ads
Create campaigns and Spark Ads via Zernio API - No TikTok Business Center developer onboarding required
Included with the Usage plan. No TikTok Business Center developer onboarding needed. Connect via OAuth and start building.
What's Supported
| Feature | Status |
|---|---|
| Standalone campaigns (Campaign > Ad Group > Ad) | Yes |
| Website conversion ads (TikTok Pixel optimization) | Yes |
| Spark Ads (boost organic videos) | Yes |
| Spark Ad custom destination URL + CTA | Yes |
Spark Code (cross-creator boosts via auth_code) | Yes |
| Bid strategy (Cost Cap, ROAS floor) | Yes |
| Campaign duplication (manual graph copy) | Yes |
Attach-to-existing-adset on /v1/ads/create | Yes |
Creative swap on PUT /v1/ads/{adId} | Yes |
| Targeting updates after creation | Yes |
| Agency Business Centers (multi-advertiser + BC list endpoint) | Yes |
| Custom Audiences (customer list) | Yes |
| Age, gender, location, interest targeting | Yes |
| Video creative from URL | Yes |
| Real-time analytics (spend, views, CTR, CPM) | Yes |
| Catalog / TikTok Shop ads | Roadmap |
| Chunked video upload + async transcode | Roadmap |
Create a Spark Ad (Boost)
const ad = await zernio.ads.boostPost({ body: {
postId: "POST_ID",
accountId: "ACCOUNT_ID",
adAccountId: "7123456789012345678",
name: "Boost viral video",
goal: "traffic",
budget: { amount: 50, type: "daily" },
schedule: { startDate: "2026-04-20", endDate: "2026-04-27" },
// TikTok-only Spark Ad creative overrides. Required for traffic /
// conversion campaigns — without linkUrl the Spark Ad has no clickable
// destination. CTA is the button label (e.g. SHOP_NOW, LEARN_MORE).
linkUrl: "https://example.com/landing",
callToAction: "SHOP_NOW",
// Optional: Cost Cap bidding. bidStrategy is the cross-platform Meta enum;
// we map it to TikTok's bid_type / bid_price automatically.
bidStrategy: "COST_CAP",
bidAmount: 0.50,
}});ad = client.ads.boost_post(
post_id="POST_ID",
account_id="ACCOUNT_ID",
ad_account_id="7123456789012345678",
name="Boost viral video",
goal="traffic",
budget={"amount": 50, "type": "daily"},
schedule={"startDate": "2026-04-20", "endDate": "2026-04-27"},
link_url="https://example.com/landing",
call_to_action="SHOP_NOW",
bid_strategy="COST_CAP",
bid_amount=0.50,
)curl -X POST "https://zernio.com/api/v1/ads/boost" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"postId": "POST_ID",
"accountId": "ACCOUNT_ID",
"adAccountId": "7123456789012345678",
"name": "Boost viral video",
"goal": "traffic",
"budget": { "amount": 50, "type": "daily" },
"schedule": { "startDate": "2026-04-20", "endDate": "2026-04-27" },
"linkUrl": "https://example.com/landing",
"callToAction": "SHOP_NOW",
"bidStrategy": "COST_CAP",
"bidAmount": 0.50
}'Spark Ads retain the creator's identity, organic engagement signals, and follower handle. linkUrl and callToAction map to landing_page_url and call_to_action on TikTok's /v2/ad/create/ creative — pass them when boosting for traffic/conversion goals so the ad has a clickable destination distinct from the original organic post.
Create a Standalone Campaign
const ad = await zernio.ads.createStandaloneAd({ body: {
accountId: "acc_tiktokads_123",
adAccountId: "7123456789012345678",
name: "Spring launch",
goal: "traffic",
budgetAmount: 100,
budgetType: "daily",
body: "Spring drop is live",
linkUrl: "https://example.com/spring",
imageUrl: "https://cdn.example.com/launch.mp4",
callToAction: "SHOP_NOW",
countries: ["US"],
ageMin: 18,
ageMax: 34,
}});ad = client.ads.create_standalone_ad(
account_id="acc_tiktokads_123",
ad_account_id="7123456789012345678",
name="Spring launch",
goal="traffic",
budget_amount=100,
budget_type="daily",
body="Spring drop is live",
link_url="https://example.com/spring",
image_url="https://cdn.example.com/launch.mp4",
call_to_action="SHOP_NOW",
countries=["US"],
age_min=18,
age_max=34,
)curl -X POST "https://zernio.com/api/v1/ads/create" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "acc_tiktokads_123",
"adAccountId": "7123456789012345678",
"name": "Spring launch",
"goal": "traffic",
"budgetAmount": 100,
"budgetType": "daily",
"body": "Spring drop is live",
"linkUrl": "https://example.com/spring",
"imageUrl": "https://cdn.example.com/launch.mp4",
"callToAction": "SHOP_NOW",
"countries": ["US"],
"ageMin": 18,
"ageMax": 34
}'TikTok's ads endpoint is video-only. Pass the video URL as imageUrl, the field name is kept for cross-platform consistency. headline is ignored on TikTok (no headline slot in TikTok creatives); body is the video caption. callToAction is supported and maps to the in-feed CTA button. Valid goal values: engagement, traffic, awareness, video_views, lead_generation, conversions, app_promotion. For goal: "conversions" you must also pass promotedObject.pixelId (see Conversion campaigns below).
Conversion campaigns
A TikTok website-conversion campaign (goal: "conversions", which Zernio maps to a CONVERSIONS objective with a WEBSITE / CONVERT ad group) needs a TikTok Pixel. Without one, TikTok rejects the ad group with 40002: Please select a pixel. Pass the pixel via promotedObject (the same cross-platform field Meta uses):
promotedObject field | Maps to TikTok | Required |
|---|---|---|
pixelId | ad group pixel_id (numeric — see note) | Yes |
customEventType | ad group optimization_event (the pixel event to optimise for) | No (auto-bid CONVERT works without it) |
customEventType takes a TikTok optimization_event code (TikTok's own UPPER_SNAKE codes, not Meta's PURCHASE/LEAD vocabulary and not PascalCase). The value must be one of the events your pixel is configured to track. Common website codes:
| Code | Pixel event |
|---|---|
ON_WEB_ORDER | Complete Payment |
INITIATE_ORDER | Place an Order |
ON_WEB_CART | Add to Cart |
ON_WEB_REGISTER | Complete Registration |
ON_WEB_DETAIL | View Content |
FORM | Submit Form |
ON_WEB_SEARCH | Search |
ON_WEB_ADD_TO_WISHLIST | Add to Wishlist |
LANDING_PAGE_VIEW | Landing Page View |
pixelId must be the numeric TikTok Pixel ID, not the alphanumeric Pixel Code shown in Events Manager (e.g. D00IKHRC77UE0J0RTNHG). TikTok's ad-group API rejects the alphanumeric code with 40002: pixel_id ... is not a valid integer string. The numeric ID is the value /pixel/list/ returns as pixel_id; if TikTok Ads Manager only surfaces the alphanumeric code for your pixel, retrieve the numeric ID via the TikTok Marketing API (GET /pixel/list/, filter by code).
Find your pixel and the events it tracks in TikTok Ads Manager → Assets → Events.
const ad = await zernio.ads.createStandaloneAd({ body: {
accountId: "acc_tiktokads_123",
adAccountId: "7123456789012345678",
name: "Spring launch - purchases",
goal: "conversions",
budgetAmount: 100,
budgetType: "daily",
body: "Spring drop is live",
linkUrl: "https://example.com/spring",
imageUrl: "https://cdn.example.com/launch.mp4",
callToAction: "SHOP_NOW",
countries: ["US"],
ageMin: 18,
ageMax: 34,
// Required for goal: "conversions" on TikTok. customEventType is optional.
promotedObject: { pixelId: "7987654321098765432", customEventType: "ON_WEB_ORDER" },
}});ad = client.ads.create_standalone_ad(
account_id="acc_tiktokads_123",
ad_account_id="7123456789012345678",
name="Spring launch - purchases",
goal="conversions",
budget_amount=100,
budget_type="daily",
body="Spring drop is live",
link_url="https://example.com/spring",
image_url="https://cdn.example.com/launch.mp4",
call_to_action="SHOP_NOW",
countries=["US"],
age_min=18,
age_max=34,
promoted_object={"pixelId": "7987654321098765432", "customEventType": "ON_WEB_ORDER"},
)curl -X POST "https://zernio.com/api/v1/ads/create" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "acc_tiktokads_123",
"adAccountId": "7123456789012345678",
"name": "Spring launch - purchases",
"goal": "conversions",
"budgetAmount": 100,
"budgetType": "daily",
"body": "Spring drop is live",
"linkUrl": "https://example.com/spring",
"imageUrl": "https://cdn.example.com/launch.mp4",
"callToAction": "SHOP_NOW",
"countries": ["US"],
"ageMin": 18,
"ageMax": 34,
"promotedObject": { "pixelId": "7987654321098765432", "customEventType": "ON_WEB_ORDER" }
}'Already set up a conversion ad group in TikTok Ads Manager (with the pixel + event configured)? Pass its ID as adSetId on POST /v1/ads/create instead. The new creative inherits the pixel and optimization_event from the parent ad group, so promotedObject isn't needed. See Attach to existing ad group.
Custom Audiences
Create a customer-file Custom Audience and upload members. adAccountId is the TikTok advertiser ID. Email only (any phone is ignored); values are SHA256-hashed server-side.
TikTok needs the member file at creation time, so the audience is created lazily on the first member upload: POST /v1/ads/audiences records it with status pending (no platform ID yet), and the first users call provisions it on TikTok. Newly created audiences take up to 48 hours to finish processing before they're targetable.
// 1. Register the audience (status: pending until the first upload)
await zernio.adaudiences.createAdAudience({
body: {
accountId: 'acc_tiktokads_123',
adAccountId: '7330955083452284929', // TikTok advertiser ID
type: 'customer_list',
name: 'Cart abandoners',
},
});
// 2. Upload members — this provisions the audience on TikTok
await zernio.adaudiences.addUsersToAdAudience({
path: { audienceId: 'aud_abc123' }, // id from the create response
body: { users: [{ email: 'jane@example.com' }, { email: 'sam@example.com' }] },
});# 1. Register the audience (status: pending until the first upload)
client.ad_audiences.create_ad_audience(
account_id="acc_tiktokads_123",
ad_account_id="7330955083452284929", # TikTok advertiser ID
type="customer_list",
name="Cart abandoners",
)
# 2. Upload members — this provisions the audience on TikTok
client.ad_audiences.add_users_to_ad_audience(
audience_id="aud_abc123", # id from the create response
users=[{"email": "jane@example.com"}, {"email": "sam@example.com"}],
)# 1. Register the audience (status: pending)
curl -X POST "https://zernio.com/api/v1/ads/audiences" \
-H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{ "accountId": "acc_tiktokads_123", "adAccountId": "7330955083452284929", "type": "customer_list", "name": "Cart abandoners" }'
# 2. Upload members — provisions the audience on TikTok (use the id from the create response)
curl -X POST "https://zernio.com/api/v1/ads/audiences/AUDIENCE_ID/users" \
-H "Authorization: Bearer YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{ "users": [{ "email": "jane@example.com" }, { "email": "sam@example.com" }] }'Media Requirements
| Type | Format | Max Size | Notes |
|---|---|---|---|
| Video | MP4, MOV, MPEG | 500 MB | 9:16 vertical, 720p+, 5-60 sec |
| Image | JPEG, PNG | 30 MB | 1080x1920 for full-screen |
Agency Business Centers
Connecting a TikTok account that owns one or more Business Centers automatically enumerates every advertiser under those BCs. There's no per-call cap — GET /v1/ads/accounts walks /v2/bc/asset/get/ (paginated server-side) and returns the full roster. Solo advertisers without a BC fall back to the OAuth-time advertiser list (a single token can typically reach a handful of advertisers without BC).
The advertiser list is cached on your connection for 1 hour and lazy-refreshed on the next call after expiry.
If you previously hit 502 Bad Gateway with a "advertiser_ids: Length must be between 1 and 100" message on agency tokens, that's resolved — the lookup now chunks /v2/advertiser/info/ calls underneath.
Bid Strategy
Pass bidStrategy (cross-platform Meta enum) on POST /v1/ads/boost, POST /v1/ads/create, or PUT /v1/ads/ad-sets/{adSetId}. Zernio maps it to TikTok's bid_type / bid_price / deep_bid_type automatically:
bidStrategy | Maps to TikTok | Required field |
|---|---|---|
LOWEST_COST_WITHOUT_CAP (default) | bid_type: BID_TYPE_NO_BID | — |
LOWEST_COST_WITH_BID_CAP | bid_type: BID_TYPE_CUSTOM + bid_price | bidAmount |
COST_CAP | bid_type: BID_TYPE_CUSTOM + bid_price | bidAmount |
LOWEST_COST_WITH_MIN_ROAS | bid_type: BID_TYPE_NO_BID + deep_bid_type: MIN_ROAS | roasAverageFloor (account must be value-optimization-enabled) |
bidAmount is in whole currency units of the ad account (USD: 5 = $5.00). On reads (GET /v1/ads/tree, GET /v1/ads/campaigns), TikTok's native bid_type is normalized back to the same Meta enum so cross-platform consumers see one shape regardless of source platform.
Spark Code (cross-creator Spark Ads)
To boost a creator's organic video from a different TikTok account than the one running the ads, the creator generates a Spark Code in their TikTok app's Promote settings and shares it with the advertiser. Pass it on POST /v1/ads/boost:
const ad = await zernio.ads.boostPost({
body: {
platformPostId: 'CREATORS_VIDEO_ID',
accountId: 'ACCOUNT_ID',
adAccountId: '7123456789012345678',
name: 'Cross-creator boost',
goal: 'traffic',
budget: { amount: 50, type: 'daily' },
linkUrl: 'https://example.com',
callToAction: 'SHOP_NOW',
sparkAuthCode: 'BCAQAAAA...',
},
});ad = client.ads.boost_post(
platform_post_id="CREATORS_VIDEO_ID",
account_id="ACCOUNT_ID",
ad_account_id="7123456789012345678",
name="Cross-creator boost",
goal="traffic",
budget={"amount": 50, "type": "daily"},
link_url="https://example.com",
call_to_action="SHOP_NOW",
spark_auth_code="BCAQAAAA...",
)curl -X POST "https://zernio.com/api/v1/ads/boost" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platformPostId": "CREATORS_VIDEO_ID",
"accountId": "ACCOUNT_ID",
"adAccountId": "7123456789012345678",
"name": "Cross-creator boost",
"goal": "traffic",
"budget": { "amount": 50, "type": "daily" },
"linkUrl": "https://example.com",
"callToAction": "SHOP_NOW",
"sparkAuthCode": "BCAQAAAA..."
}'Without sparkAuthCode, boosts are limited to videos owned by the same TikTok account that's running the ads. Maps to auth_code on TikTok's AdcreateCreatives.
Attach to existing ad group
POST /v1/ads/create with adSetId set creates a new ad inside an existing TikTok ad group, skipping the campaign + ad-group create. Bid strategy, budget, targeting, and goal are inherited from the existing ad group. Use this when you've configured one ad group manually in TikTok Ads Manager and just want to add Zernio-managed creatives to it.
Creative swap
PUT /v1/ads/{adId} accepts a creative object to replace the live creative on an existing TikTok ad. Patch-style — only fields you supply are touched. headline is ignored (no slot on TikTok); body becomes ad_text; linkUrl becomes landing_page_url; videoUrl triggers a fresh upload.
Targeting update after creation
PUT /v1/ads/{adId} accepts targeting for TikTok ads — Zernio forwards it to /v2/adgroup/update/ with the same field set as create. (Pinterest / X / LinkedIn / Google return 501; those platforms force re-creation for targeting changes.)
Campaign duplication
POST /v1/ads/campaigns/{campaignId}/duplicate works on TikTok via a manual graph walk: Zernio reads the source campaign + ad groups + ads, then recreates each entity with their bid configuration, targeting, schedule, and creative fields preserved. Spark Ad linkage (tiktok_item_id) carries over. Everything is created paused so you can review before launching.