TikTok Ads
Run Spark Ads, standalone video campaigns and website conversion campaigns on a tiktokads account, with Custom Audiences and Insights.
Boost a TikTok video as a Spark Ad with POST /v1/ads/boost, or create a campaign, ad group and video ad in one POST /v1/ads/create call, on a tiktokads account. No TikTok Business Center developer onboarding is needed: connect with OAuth and call the ads endpoints. Zernio's adSetId is the TikTok ad group id (on Meta the same field is an ad set, on LinkedIn a campaign).
Quick reference
| Feature | Status |
|---|---|
| Standalone campaigns (Campaign > Ad Group > Ad) | Yes |
| Website conversion ads (TikTok Pixel optimization) | Yes |
| TikTok Pixel management, pixel events, Business Center sharing and Shopify / WordPress install (Pixels) | Yes |
| Spark Ads (boost organic videos) | Yes |
| Spark Ad custom destination URL and 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 an existing ad group on /v1/ads/create | Yes |
Creative swap on PUT /v1/ads/{adId} | Yes |
| Targeting updates after creation | Yes |
| Agency Business Centers (multi-advertiser plus a BC list endpoint) | Yes |
| Custom Audiences (customer list) | Yes |
| Age, gender, location and interest targeting | Yes |
| Video creative from URL | Yes |
| Insights (spend, views, CTR, CPM) | Yes |
Live reports and de-duplicated reach on GET /v1/ads/insights | Yes |
| Catalog and TikTok Shop ads | Roadmap |
| Chunked video upload and async transcode | Roadmap |
Before you start
TikTok Ads requires a tiktokads account with its own token: the TikTok Business API is a separate OAuth from TikTok posting. It does not use per-request OAuth scopes; when the user authorizes the connection, permissions are granted at the app level (ad account management, reporting, creative management) rather than as individual scopes in the consent URL. You also need the advertiser id, from List ad accounts.
Connect
Call GET /v1/connect/tiktok/ads with profileId and, optionally, the accountId of a TikTok posting account (Connect ads). With accountId, the new tiktokads account links to that posting account, so Spark Ads and standalone ads with the real @username identity become available. Without it, ads-only mode applies: standalone ads use a synthetic Brand Identity (CUSTOMIZED_USER, configured with PATCH /v1/connect/tiktok-ads or inline through brandIdentity on create) and Spark Ads are unavailable, because TikTok requires a posting account for them.
curl "https://zernio.com/api/v1/connect/tiktok/ads?profileId=66a1f0c2a4b9d3e8f1a2b3c4&accountId=66b2e19d8c3f5a7e9d0b1c2d" \
-H "Authorization: Bearer $ZERNIO_API_KEY"Response (200):
{
"authUrl": "https://business-api.tiktok.com/portal/auth?app_id=...",
"state": "..."
}Send the user to authUrl, as in the connecting accounts guide. Once the flow completes, the same call returns alreadyConnected: true with the tiktokads accountId, which every sample below uses.
Create ads
Spark Ads (boost)
Call POST /v1/ads/boost with postId, accountId, adAccountId, goal and budget. Spark Ads keep the creator's identity, organic engagement signals and follower handle.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const { data: boosted } = await zernio.adcampaigns.boostPost({
body: {
postId: '65f1c0a9e2b5af0012ab34cd',
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
adAccountId: '7123456789012345678',
name: 'Boost viral video',
goal: 'traffic',
budget: { amount: 50, type: 'daily' },
schedule: { startDate: '2027-01-04T09:00:00Z', endDate: '2027-01-11T23:59:00Z' },
targeting: { countries: ['US'] },
linkUrl: 'https://example.com/landing',
callToAction: 'SHOP_NOW',
bidStrategy: 'COST_CAP',
bidAmount: 0.5
}
});
console.log(boosted.ad._id);Response (201):
{
"ad": {
"_id": "66f0a1b2c3d4e5f6a7b8c9d0",
"name": "Boost viral video",
"platform": "tiktok",
"status": "pending_review",
"adType": "boost",
"goal": "traffic",
"budget": { "amount": 50, "type": "daily" },
"bidStrategy": "COST_CAP",
"bidAmount": 0.5,
"platformAdId": "1802345678901234567",
"platformCampaignId": "1802345678901234001",
"platformAdSetId": "1802345678901234002"
},
"message": "Ad created"
}linkUrl and callToAction are the Spark Ad creative overrides, mapped to landing_page_url and call_to_action on TikTok's /v2/ad/create/. Pass them for traffic and conversion goals: without linkUrl the Spark Ad has no clickable destination distinct from the organic video. targeting.countries is required on TikTok boosts (the ad group needs location_ids). bidStrategy and bidAmount are optional Cost Cap bidding (Bid strategy). Every sample on this page reuses the zernio and client constructors from this one.
Standalone campaign
Call POST /v1/ads/create. TikTok's ads endpoint is video-only, so the video URL goes in imageUrl (the field keeps its name for cross-platform consistency), body is the video caption, and headline is ignored because TikTok creatives have no headline slot. Valid goal values are engagement, traffic, awareness, video_views, lead_generation, conversions and app_promotion.
const { data: created } = await zernio.adcampaigns.createStandaloneAd({
body: {
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
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
}
});
console.log(created.ad.platformAdSetId);Response (201), same shape as the boost with "adType": "standalone":
{
"ad": {
"_id": "66f0a1b2c3d4e5f6a7b8c9d1",
"name": "Spring launch",
"platform": "tiktok",
"status": "pending_review",
"adType": "standalone",
"goal": "traffic",
"platformAdId": "1802345678901234568",
"platformCampaignId": "1802345678901234003",
"platformAdSetId": "1802345678901234004"
},
"message": "Ad created"
}platformAdSetId is the new TikTok ad group.
Conversion campaigns
A 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 through promotedObject, the same cross-platform field Meta uses:
promotedObject field | Maps to TikTok | Required |
|---|---|---|
pixelId | ad group pixel_id (numeric) | 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 or LEAD vocabulary and not PascalCase), or the exact event name shown in TikTok Events Manager. 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 (for example D00IKHRC77UE0J0RTNHG). TikTok's ad-group API rejects the code with 40002: pixel_id ... is not a valid integer string. The numeric id is the pixel_id that TikTok's GET /pixel/list/ returns. If Ads Manager only shows the alphanumeric code for your pixel, list your pixels: each tag's id is the numeric id and siteTagId is the code.
Find the pixel and the events it tracks in TikTok Ads Manager under Assets, then Events. The request is the standalone create plus promotedObject:
{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"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" }
}If a conversion ad group already exists in TikTok Ads Manager with the pixel and 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 is not needed (Attach to an existing ad group).
Spark Code for cross-creator boosts
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 as sparkAuthCode on POST /v1/ads/boost, with the creator's video as platformPostId:
{
"platformPostId": "7310234567890123456",
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"adAccountId": "7123456789012345678",
"name": "Cross-creator boost",
"goal": "traffic",
"budget": { "amount": 50, "type": "daily" },
"targeting": { "countries": ["US"] },
"linkUrl": "https://example.com",
"callToAction": "SHOP_NOW",
"sparkAuthCode": "BCAQAAAA..."
}Without sparkAuthCode, boosts are limited to videos owned by the same TikTok account that runs the ads. The value maps to auth_code on TikTok's AdcreateCreatives.
Bid strategy
Pass bidStrategy (the 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 and deep_bid_type:
bidStrategy | Maps to TikTok | Required field |
|---|---|---|
LOWEST_COST_WITHOUT_CAP (default) | bid_type: BID_TYPE_NO_BID | none |
LOWEST_COST_WITH_BID_CAP | bid_type: BID_TYPE_CUSTOM plus bid_price | bidAmount |
COST_CAP | bid_type: BID_TYPE_CUSTOM plus bid_price | bidAmount |
LOWEST_COST_WITH_MIN_ROAS | bid_type: BID_TYPE_NO_BID plus deep_bid_type: MIN_ROAS | roasAverageFloor (the account must be value-optimization-enabled) |
bidAmount is in whole currency units of the ad account (USD: 5 is $5.00). On reads (GET /v1/ads/tree, GET /v1/ads/campaigns), TikTok's native bid_type is normalized back to the same enum, so cross-platform consumers see one shape regardless of source platform.
Media requirements
| Type | Format | Max size | Notes |
|---|---|---|---|
| Video | MP4, MOV, MPEG | 500 MB | 9:16 vertical, 720p or better, 5 to 60 seconds |
| Image | JPEG, PNG | 30 MB | 1080 x 1920 for full-screen |
Videos are fetched from the URL you pass; the media uploads guide covers hosting them on Zernio.
Target
Custom Audiences
Create a customer-file Custom Audience with POST /v1/ads/audiences (type: "customer_list"), then upload members with POST /v1/ads/audiences/{audienceId}/users. adAccountId is the TikTok advertiser id. TikTok matches on email only (any phone is ignored); values are SHA-256 hashed server-side.
TikTok needs the member file at creation time, so the audience is created lazily on the first member upload: the create records it with status pending (no platform id yet), and the first users call provisions it on TikTok. A new audience takes up to 48 hours to finish processing before it is targetable.
const { data: audience } = await zernio.adaudiences.createAdAudience({
body: {
accountId: '66b2e19d8c3f5a7e9d0b1c2d',
adAccountId: '7123456789012345678',
type: 'customer_list',
name: 'Cart abandoners'
}
});
const { data: upload } = await zernio.adaudiences.addUsersToAdAudience({
path: { audienceId: audience.audience.id },
body: { users: [{ email: 'jane@example.com' }, { email: 'sam@example.com' }] }
});
console.log(upload.numReceived);Response (201):
{
"audience": { "id": "66e5b2c3d4f5a6b7c8d9e0f1", "name": "Cart abandoners", "type": "customer_list", "platform": "tiktok", "status": "pending" },
"message": "Audience created"
}Response (200):
{ "message": "Users added", "numReceived": 2, "numInvalid": 0 }Targeting fields
countries, ageMin, ageMax, gender, languages (ISO 639-1; zh_TW targets traditional Chinese), interests (from GET /v1/ads/interests), behaviors, regions, cities, zips, metros and incomeTier apply on create; audienceInclude and audienceExclude take the platformAudienceId of a processed Custom Audience.
behaviors are TikTok's video and creator interaction categories, from GET /v1/ads/targeting/search?dimension=behavior. Ids look like video:1913101 (people who watched to the end, liked, commented on or shared videos in that category in the last 15 days) or creator:24001 (people who followed or viewed the profile of creators in that category). TikTok only accepts them with the TikTok placement, so the ad group runs on TikTok alone when behaviors is set. To change targeting after creation, see Targeting update after creation.
Measure
Call GET /v1/ads/{adId}/analytics with the ad's _id for spend, impressions, clicks, CTR, CPM and video metrics, and breakdowns for a split by gender, age, country_code, platform, ac or language.
curl "https://zernio.com/api/v1/ads/66f0a1b2c3d4e5f6a7b8c9d0/analytics?fromDate=2027-01-04&toDate=2027-01-11&breakdowns=age" \
-H "Authorization: Bearer $ZERNIO_API_KEY"Response (200), trimmed:
{
"ad": { "id": "66f0a1b2c3d4e5f6a7b8c9d0", "name": "Boost viral video", "platform": "tiktok", "status": "active", "currency": "USD" },
"analytics": {
"summary": { "spend": 312.4, "impressions": 128400, "clicks": 2110, "ctr": 1.64, "cpc": 0.15, "cpm": 2.43, "videoPlayActions": 96200, "videoP100WatchedActions": 18400 },
"daily": [ { "date": "2027-01-04", "spend": 44.1, "impressions": 18200, "clicks": 301 } ],
"breakdowns": { "age": [ { "value": "AGE_18_24", "spend": 140.2, "impressions": 61000, "clicks": 1080 } ] }
}
}GET /v1/ads/tree rolls the same metrics up per campaign and ad group (Get campaign tree). For offline conversions (in-store, CRM, call-center), POST /v1/ads/conversions on a tiktokads account sends them to TikTok's Offline Events API: destinationId is the Offline Event Set id, each event must carry an email or phone, and the connection must have granted the Offline Events permission (older grants must reconnect). Web-pixel events are not sent this way (Send conversions).
Live reports and de-duplicated reach
GET /v1/ads/insights runs TikTok's synchronous report live, for questions the synced metrics cannot answer. The most common one is reach across a set: adding the reach of two campaigns counts a person who saw both twice, and the synced numbers only hold per-entity reach.
Send accountId (a tiktok or tiktokads connection), adAccountId (the advertiser id), dataLevel (AUCTION_ADVERTISER, AUCTION_CAMPAIGN, AUCTION_ADGROUP or AUCTION_AD), dimensions (1 to 4 TikTok dimensions, comma-separated), metrics (TikTok metric names, comma-separated) and fromDate/toDate (TikTok caps the span at 365 days). reportType is BASIC (the default) or AUDIENCE. filtering takes the same JSON array as Meta, [{"field", "operator", "value"}], with TikTok's field names (campaign_ids, adgroup_ids, ad_ids) and TikTok's filter type as operator, for example IN.
For de-duplicated reach, filter the set and group by country_code instead of the entity id. TikTok then counts each person once across the whole set and date range. Two campaigns over a month:
curl -G "https://zernio.com/api/v1/ads/insights" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
--data-urlencode "accountId=66b2e19d8c3f5a7e9d0b1c2d" \
--data-urlencode "adAccountId=7123456789012345678" \
--data-urlencode "dataLevel=AUCTION_CAMPAIGN" \
--data-urlencode "dimensions=country_code" \
--data-urlencode "metrics=reach,impressions,frequency" \
--data-urlencode "fromDate=2027-01-01" \
--data-urlencode "toDate=2027-01-31" \
--data-urlencode 'filtering=[{"field":"campaign_ids","operator":"IN","value":["1876574798182050","1876575504573650"]}]'Response (200), trimmed:
{
"adAccountId": "7123456789012345678",
"data": [
{ "dimensions": { "country_code": "US" }, "metrics": { "reach": "17770000", "impressions": "41210000", "frequency": "2.32" } }
],
"paging": { "page": 1, "pageSize": 100, "totalRows": 1, "totalPages": 1 }
}Rows come back verbatim from TikTok, with metric values as strings; page with page and pageSize (up to 1,000). For a set of ads, use dataLevel=AUCTION_AD with ad_ids, and for ad groups AUCTION_ADGROUP with adgroup_ids.
- Reach is per country. When the set delivers in several countries, summing the rows counts a person reached in two countries twice.
- The filter must match the data level or a coarser one. A finer filter, such as
ad_idsatAUCTION_CAMPAIGN, answers400: TikTok would silently widen it to every campaign containing those ads. AUCTION_ADVERTISERtakes no entity filter.- A
400carries TikTok's own message when TikTok rejects the query (an unknown metric, a bad dimension combination), a403means the connection cannot read that advertiser, and a429is TikTok's rate limit.
Asynchronous report runs (POST /v1/ads/insights/reports) stay Meta only.
Pixels
Manage TikTok Pixels through the platform-neutral tracking tags API on your tiktokads connection: list, read, create, rename and configure a pixel, manage its events, share it through Business Center, read its event counts, and install it on a connected Shopify store or WordPress site.
| Field | Value |
|---|---|
tagId | The numeric pixel_id: what promotedObject.pixelId takes on conversion campaigns |
siteTagId | The alphanumeric pixel_code (for example D00IKHRC77UE0J0RTNHG) that ttq.load() and Events Manager use |
adAccountId | The advertiser id. Optional on per-pixel calls: without it Zernio searches every advertiser on the connection, and a pixel shared into several advertisers resolves to its owner's copy |
events | On GET .../tracking-tags/{tagId} and GET .../events: the pixel's events with TikTok's type (SHOPPING, ON_WEB_CART, ...), the matching siteEvent, and siteEventId, the name ttq.track() fires (Purchase, AddToCart, ...) |
const { data } = await zernio.trackingtags.createTrackingTag({
path: { accountId: '66b2e19d8c3f5a7e9d0b1c2d' },
body: { adAccountId: '7301234567890123456', name: 'Storefront pixel' }
});Response (201):
{
"platform": "tiktokads",
"tag": {
"id": "7412345678901234567",
"siteTagId": "D00IKHRC77UE0J0RTNHG",
"name": "Storefront pixel",
"platform": "tiktokads",
"kind": "pixel",
"status": "active",
"creationTime": 1804064400,
"ownerAdAccountId": "7301234567890123456",
"code": "<script>!function (w, d, t) {...}</script>",
"events": []
}
}Pixel names are at most 40 characters with no emojis. The create is not idempotent and TikTok has no pixel delete, so never retry it blindly; list the pixels after a timeout.
Creating an event is not idempotent either, and TikTok returns no id for it and refreshes a pixel's event list only every few hours. When the new event is not listed yet, the response carries id: "" and status: "pending"; read the events again later for its id.
| Operation | Behavior |
|---|---|
Update (PATCH .../tracking-tags/{tagId}) | name, enableAutomaticMatching, automaticMatchingFields (Meta's codes folded onto TikTok's email, phone, name, address and external id switches; gender and date of birth answer 400), and first-party cookies as enableFirstPartyCookies (boolean) or firstPartyCookieStatus. Other fields answer 400. |
Stats (GET .../stats) | Per-event counts from TikTok's /pixel/event/stats, 7 days by default and at most 30 per request, bucketed by whole UTC days. TikTok reports no last-fired time. |
Share (.../shared-accounts) | Business Center pixel links: list, link and unlink advertisers. The pixel must be an asset of a Business Center the connection manages (its owner Business Center, or one of the connection's); otherwise 400 asking you to transfer it into Business Center (Assets, Events). sharedAccounts[].businessId is the Business Center. |
Conversion events (.../events) | List, create and update the pixel's events. Create takes type (TikTok's event type) or siteEvent, plus optional name, defaultValue and currency (USD, JPY or INR). Update changes name, defaultValue and currency only: TikTok cannot change an event's type, so create a new event instead. DELETE answers 501 platform_not_supported: TikTok's delete call reports success without removing the event, so delete events in TikTok Events Manager. |
| Diagnostics | 501: TikTok has no pixel diagnostics endpoint (/pixel/diagnostic/, /pixel/diagnostics/ and /pixel/health/ answer 404). Use the stats or Events Manager. |
| Users and partners | 501. |
| Delete | Not offered: TikTok has no pixel delete endpoint (/pixel/delete/ answers 404, and it is not in TikTok's SDK specs). |
| Install | Shopify and WordPress. WordPress gets TikTok's base code (ttq.load plus ttq.page); Shopify sends each storefront event to analytics.tiktok.com/api/v2/pixel, the endpoint TikTok's own pixel script calls (undocumented), with the ttclid click id kept first-party, the _ttp browser id reused and an event id for dedup with the Events API. |
A connection authorized before TikTok's Pixel Management permission answers every pixel call with 403: reconnect TikTok Ads to grant it.
Operate
Attach to an existing ad group
POST /v1/ads/create with adSetId creates a new ad inside an existing TikTok ad group, skipping the campaign and ad-group create. Bid strategy, budget, targeting and goal are inherited from the ad group. Use it when one ad group was configured by hand in TikTok Ads Manager and you want to add Zernio-managed creatives to it.
Ad comments
GET /v1/ads/{adId}/comments returns the comments on a TikTok ad in the same normalized shape it returns for Meta, and TikTok also supports moderation: reply, hide or restore, and delete. Comments on organic posts are read and moderated through the inbox instead; this endpoint is the one that reaches comments on ad creatives, dark posts included.
TikTok reads comments over a date window rather than all of history, so the window is at most 30 days and defaults to the last 30 days. See Ad comments for the endpoints, the window rules and the canReply / canDelete / canHide flags.
Listing uses the stored ad group without resolving identity or looking up the video item ID. meta.tiktokItemId: null does not block it, and there is no ad-level identity or item-ID check to perform first. canReply and canDelete are per comment and false when the identity or item needed to moderate is unknown. Hiding does not require an identity. A direct reply or delete can resolve missing fields lazily; use the returned capabilities to guide the UI without blocking comment reads.
Creative swap
PUT /v1/ads/{adId} accepts a creative object that replaces the live creative on an existing TikTok ad, patch-style: only the fields you supply are touched. headline is ignored (no slot on TikTok), body becomes ad_text, linkUrl becomes landing_page_url, and videoUrl triggers a fresh upload.
curl -X PUT "https://zernio.com/api/v1/ads/66f0a1b2c3d4e5f6a7b8c9d0" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "creative": { "body": "Spring drop, last week", "videoUrl": "https://cdn.example.com/launch-v2.mp4" } }'Response (200):
{
"ad": { "_id": "66f0a1b2c3d4e5f6a7b8c9d0", "platform": "tiktok", "status": "pending_review", "creative": { "body": "Spring drop, last week" } },
"message": "Ad updated"
}Targeting update after creation
PUT /v1/ads/{adId} accepts targeting for TikTok ads with the same field set as create; Zernio forwards it to /v2/adgroup/update/. The other networks are narrower: Pinterest and X return 501, Google accepts keyword and device bid adjustments only, and LinkedIn accepts countries only.
Campaign duplication
POST /v1/ads/campaigns/{campaignId}/duplicate with platform: "tiktok" works through a manual graph walk: Zernio reads the source campaign, ad groups and ads, then recreates each entity with 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; the response carries copiedCampaignId.
Agency Business Centers
Connecting a TikTok account that owns one or more Business Centers enumerates every advertiser under those BCs. There is no per-call cap: GET /v1/ads/accounts walks /v2/bc/asset/get/ (paginated server-side), chunks the /v2/advertiser/info/ lookup underneath, 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 a BC). The advertiser list is cached on the connection for 1 hour and refreshed on the next call after expiry. GET /v1/ads/business-centers?accountId= lists the BCs themselves with an advertiserCount each, for agency-style pickers.
What you cannot do
TikTok Ads through Zernio does not support:
- Catalog and TikTok Shop ads.
goalhas no catalog option on TikTok, and a catalog campaign built in Ads Manager syncs back asconversions. - TikTok Instant Forms.
goal: "lead_generation"builds a Smart+ lead campaign that sends the click to your own form onlinkUrl. - Spark Ads on an ads-only connection, because TikTok requires a posting account for them. Connect the TikTok account itself, not the ad account alone.
- Chunked video upload and async transcode. A video creative transfers in one request, under the 500 MB ceiling in media requirements.
- Creating an ad group on its own.
POST /v1/ads/ad-setsis Google Ads only and answers501everywhere else. - The Meta-only reads and tools. Ad previews, the creative library, reach and frequency, the change log, ad labels, A/B studies, account finance, the image and video libraries and asynchronous insights reports all answer
501on TikTok.
Common errors
| Error | Cause | Fix |
|---|---|---|
422 with code ads_connection_required | No tiktokads account for the profile, or the TikTok user is not authorized as an Identity on the advertiser | Connect TikTok Ads, or authorize the identity in TikTok Ads Manager. |
40002: Please select a pixel | goal: "conversions" without promotedObject.pixelId | Pass the numeric pixel id, or attach to an existing conversion ad group with adSetId. |
40002: pixel_id ... is not a valid integer string | The alphanumeric Pixel Code was sent instead of the numeric id | Use the pixel's id from GET /v1/accounts/{accountId}/tracking-tags (Pixels). |
400 on a boost | countries missing, or callToAction sent without linkUrl | Send targeting.countries, and always pair the CTA with a destination. |
| Budget rejected | Below TikTok's minimum | TikTok's minimum is $20 per ad set. |
A 422 on any ads call means the profile has no TikTok Ads connection:
{
"error": "TikTok Ads is not connected for this profile",
"type": "invalid_request_error",
"code": "ads_connection_required",
"param": "accountId"
}Error handling covers the envelope.
Related
- Connecting accounts: the OAuth flow behind
authUrl. - TikTok: the posting account Spark Ads boost from.
- Boost post and Create standalone ad: every field.
- Meta Ads: the same endpoints on Meta, where
adSetIdis an ad set. - Pixels and tracking tags: the pixel API across platforms.