Boost post as ad
Creates a paid ad from an existing published post, keeping the post's engagement. By default it provisions the whole hierarchy (campaign, ad set, ad).
Attach shape (Meta). Send adSetId to put the ad under an EXISTING
ad set instead, so that ad set keeps its learning phase. It then owns
budget, schedule and targeting, and sending any of those alongside
adSetId is a 400 rather than a silent drop. budget is required only
without adSetId.
instagramAccountId, destinationType and adSetId are Meta-only and
return 400 on other platforms.
Retries. Boosts are NOT idempotent and can take minutes when Meta requires re-hosting an Instagram video, so do not retry on client timeout. Send an Idempotency-Key header to make retries safe: same key and body replays the original 201, and distinct keys always create distinct ads. Without the header, an identical request is treated as a retry: while one is in flight it returns 409, and within 10 minutes of a completed boost it returns the already-created ad instead of creating another. To intentionally duplicate an ad, send distinct Idempotency-Keys (or vary the body, e.g. the name).
API key authentication - use your Zernio API key as a Bearer token
In: header
Header Parameters
Optional client-generated unique key (e.g. a UUID) that makes retries safe. Same key + same body replays the original response; same key + different body → 422; key still processing → 409.
length <= 255Zernio post ID (provide this or platformPostId)
Platform post ID (alternative to postId)
Social account ID
Platform ad account ID
length <= 255Available goals vary by platform. Meta (Facebook/Instagram) and TikTok support all 7. LinkedIn supports all except app_promotion. Twitter/X supports engagement, traffic, awareness, video_views, app_promotion. Pinterest and Google Ads support only engagement, traffic, awareness, video_views.
"engagement" | "traffic" | "awareness" | "video_views" | "lead_generation" | "conversions" | "app_promotion"Meta only. Attach the boosted post to this existing ad set instead of creating a campaign. The ad set then owns budget, schedule and targeting; sending those too is a 400.
Required unless adSetId is set.
Meta only. Instagram identity the ad runs AS (creative.instagram_user_id), overriding the account linked to the Page. Live-verified against a Page-post creative.
Meta only. Ad-set destination_type — where the click LANDS, as opposed to instagramAccountId which is who the ad runs as. Lead ads force ON_AD and ignore this.
"INSTAGRAM_PROFILE" | "WEBSITE" | "ON_AD" | "MESSENGER" | "WHATSAPP"ISO 4217 currency code matching the ad account's currency. Meta only. Optional: Zernio resolves it from the ad account when omitted. The value selects the minor-unit exponent Zernio converts budget/bid amounts by before calling Meta (most currencies are cents; zero-decimal currencies like JPY/KRW are sent as-is).
3 <= length <= 3Same geo/demographic fields as the TargetingSpec used by /v1/ads/create.
Geo keys (regions/cities/zips/metros) resolve via
GET /v1/ads/targeting/search?dimension=geo. City radius and lat/lng
customLocations are Meta-only and preserve the boosted post's
social proof (the ad references the existing post).
Meta only. A Meta-native targeting spec (e.g.
{ "geo_locations": { "cities": [{ "key": "...", "radius": 15, "distance_unit": "kilometer" }] } }).
Sent alone it is forwarded unchanged. Use for advanced fields the structured
object does not expose (flexible_spec, excluded audiences, business places,
user_os, wireless_carrier).
Can be combined with targeting: rawTargeting is the BASE layer and the
built camelCase spec is merged on top, key by key (camelCase wins on
collision). The merge goes one level deep inside geo_locations and
excluded_geo_locations (built sub-keys win; raw-only sub-keys such as
location_types survive). Array values (flexible_spec, ...) are replaced
as a whole key, never element-merged.
When rawTargeting is present the advantage_audience: 0 default that
Zernio normally applies is no longer emitted, so it cannot clobber a
targeting_automation sent in the raw spec. Meta requires
targeting_automation on ad set creation, so include it in the raw spec,
or send targeting.advantage_audience (0 or 1), which is merged over raw
as targeting_automation.
"LOWEST_COST_WITHOUT_CAP" | "LOWEST_COST_WITH_BID_CAP" | "COST_CAP" | "LOWEST_COST_WITH_MIN_ROAS"Deprecated: send it inside platformSpecificData instead (Meta today; TikTok's nested shape is planned). The flat field keeps working during the deprecation window; sending both shapes returns a 400.
Bid cap in WHOLE currency units (USD: 5 = $5.00; JPY: 100 = ¥100). Required when
bidStrategy is LOWEST_COST_WITH_BID_CAP or COST_CAP. Backward-compat: providing
bidAmount without bidStrategy is treated as LOWEST_COST_WITH_BID_CAP.
Deprecated: send it inside platformSpecificData instead (Meta today; TikTok's nested shape is planned). The flat field keeps working during the deprecation window; sending both shapes returns a 400.
Minimum ROAS as a decimal multiplier (e.g. 2.0 = 2.0x ROAS). Required when
bidStrategy is LOWEST_COST_WITH_MIN_ROAS. Sent to Meta as
bid_constraints.roas_average_floor × 10000 (Meta uses fixed-point integers).
Platform-specific options. The platform is derived from accountId;
sending options for a different platform returns a 400. LinkedIn
(campaign bidding and delivery controls) and Meta (the bid trio)
have options today.
Meta: bidStrategy, bidAmount and roasAverageFloor may be
sent here instead of at the root — the preferred home going forward.
Sending the bid fields in BOTH places returns a 400
(mutually_exclusive_fields).
Meta only. Tracking specs (pixel, URL tags).
Meta only. Required for housing, employment, credit, or political ads.
Meta (metaads) only. 2-letter ISO country codes the special ad category applies to. Requires specialAdCategories to be set (400 otherwise).
Destination URL for the CTA button. Send it together with callToAction.
Meta: adds a top-level call_to_action to the post-reference creative.
This is what gives a traffic boost a clickable destination without
replacing the creative and losing the post's social proof. Ignored when
leadGenFormId is set, which supplies its own destination. Live-verified
against a Page-post creative.
TikTok: maps to landing_page_url on the Spark Ad creative
(AdcreateCreatives.landing_page_url); Spark Ads have no clickable
destination without it.
Ignored on LinkedIn / Pinterest / X / Google, which infer the destination from the boosted post.
uriCTA button label. Send it together with linkUrl — a CTA without a
destination produces a button that goes nowhere, so sending one alone is a 400.
Meta: validated against the Meta CTA enum (same values as
POST /v1/ads/create), e.g. LEARN_MORE, SHOP_NOW, SIGN_UP.
TikTok: pass-through to call_to_action on the Spark Ad creative; the
platform validates the value. See TikTok's "Enumeration - Call-to-Action".
TikTok-only. Spark Code (creator's auth_code) authorizing cross-creator
Spark Ads — the advertiser can boost a video owned by a DIFFERENT TikTok
account. Without this, boosts are limited to videos owned by the same
account running the ads (same-BC creators only). The creator generates the
code in their TikTok app's Promote settings and shares it with the
advertiser. Maps to auth_code on the creative entry of /v2/ad/create/.
Legal entity that benefits from the ad. Required when targeting EU users
(EU DSA, Article 26). Optional if the ad account has a default beneficiary:
set it once via PATCH /v1/ads/accounts or in Meta Ads Manager, and Meta
fills it in whenever the field is omitted.
length <= 100Legal entity that pays for the ad. Can differ from dsaBeneficiary
(for example, an agency paying for a client's ads). Same rules as
dsaBeneficiary: required for EU targeting unless the ad account has
a default payor.
length <= 100Meta only. Explicit ad-set optimization_goal override. When omitted,
defaults to the value derived from goal. The value must be compatible
with the objective Meta derives from goal, not with the objective used
by POST /v1/ads/create for the same goal name: boost maps goal: "engagement" to objective OUTCOME_AWARENESS, which accepts
REACH, IMPRESSIONS, AD_RECALL_LIFT, or THRUPLAY-class values, and
rejects POST_ENGAGEMENT (that value is only valid under
OUTCOME_ENGAGEMENT, which create uses for the same goal name).
Response Body
application/json
application/json
import Zernio from '@zernio/node';const zernio = new Zernio({ apiKey: process.env.ZERNIO_API_KEY });const { data } = await zernio.adcampaigns.boostPost({ body: { accountId: 'account_abc123', adAccountId: 'adaccount_abc123', name: 'Example', goal: 'engagement', },});console.log(data);{
"ad": {
"_id": "string",
"name": "string",
"platform": "facebook",
"status": "active",
"configuredStatus": "ACTIVE",
"reviewStatus": "in_review",
"adType": "boost",
"creativeType": "video",
"goal": "engagement",
"isExternal": true,
"budget": {
"amount": 0,
"type": "daily"
},
"metrics": {
"spend": 0,
"impressions": 0,
"reach": 0,
"clicks": 0,
"ctr": 0,
"cpc": 0,
"cpm": 0,
"engagement": 0,
"conversions": 0,
"costPerConversion": 0,
"actions": {
"link_click": 160,
"post_engagement": 300,
"offsite_conversion.fb_pixel_purchase": 42
},
"actionValues": {
"offsite_conversion.fb_pixel_purchase": 2456.78,
"offsite_conversion.fb_pixel_add_to_cart": 980.5
},
"purchaseValue": 0,
"roas": 0,
"costPerAction": {
"link_click": 0.1052,
"offsite_conversion.fb_pixel_purchase": 4.0114
},
"outboundClicks": 0,
"outboundClicksCtr": 0,
"inlineLinkClicks": 0,
"inlineLinkClickCtr": 0,
"uniqueClicks": 0,
"uniqueCtr": 0,
"videoPlayActions": 0,
"video30SecWatchedActions": 0,
"videoThruplayWatchedActions": 0,
"videoP25WatchedActions": 0,
"videoP50WatchedActions": 0,
"videoP75WatchedActions": 0,
"videoP95WatchedActions": 0,
"videoP100WatchedActions": 0,
"videoAvgTimeWatchedActions": 0,
"costPerThruplay": 0,
"funnel": {
"landingPageViews": 0,
"contentViews": 0,
"searches": 0,
"wishlistAdds": 0,
"cartAdds": 0,
"checkoutsInitiated": 0,
"paymentInfoAdds": 0,
"purchases": 0,
"leads": 0,
"registrationsCompleted": 0,
"appInstalls": 0,
"messagingConversationsStarted": 0,
"messagingFirstReplies": 0
},
"engagementBreakdown": {
"postEngagement": 0,
"pageEngagement": 0,
"reactions": 0,
"comments": 0,
"shares": 0,
"saves": 0,
"pageLikes": 0,
"videoViews": 0,
"linkClicks": 0
},
"lastSyncedAt": "2019-08-24T14:15:22Z"
},
"platformAdId": "string",
"platformAdAccountId": "string",
"platformCampaignId": "string",
"platformAdSetId": "string",
"campaignName": "string",
"adSetName": "string",
"platformObjective": "OUTCOME_SALES",
"optimizationGoal": "OFFSITE_CONVERSIONS",
"costType": "CPC",
"servingStatuses": [
"ACCOUNT_TOTAL_BUDGET_HOLD"
],
"platformAdAccountName": "Zernio - previously Late",
"platformCreatedAt": "2019-08-24T14:15:22Z",
"bidStrategy": "LOWEST_COST_WITHOUT_CAP",
"bidAmount": 5,
"roasAverageFloor": 2,
"promotedObject": {
"custom_event_type": "PURCHASE",
"pixel_id": "string",
"page_id": "string",
"application_id": "string",
"product_set_id": "string"
},
"creative": {
"thumbnailUrl": "string",
"imageUrl": "string",
"videoId": "string",
"videoUrl": "string",
"objectType": "string",
"objectStoryId": "string",
"effectiveObjectStoryId": "string",
"pageId": "string",
"effectiveInstagramMediaId": "string",
"instagramUserId": "string",
"instagramPermalinkUrl": "string",
"mediaUrls": [
"string"
],
"isServing": true,
"servingHoldReasons": [
"UNDER_REVIEW"
],
"body": "string",
"googleHeadline": "string",
"googleDescription": "string",
"linkUrl": "string",
"pinterestImageUrl": "string",
"pinterestTitle": "string",
"pinterestDescription": "string"
},
"targeting": {},
"schedule": {
"startDate": "2019-08-24T14:15:22Z",
"endDate": "2019-08-24T14:15:22Z"
},
"rejectionReason": "string",
"createdAt": "2019-08-24T14:15:22Z",
"updatedAt": "2019-08-24T14:15:22Z"
},
"message": "string"
}{
"error": "Unauthorized"
}Pause or resume many campaigns POST
Process up to 50 campaigns in one call. Each campaign is updated concurrently and the response contains a per-campaign result so a single bad row does not fail the whole batch.
Duplicate a campaign POST
Duplicates a campaign, including its ad sets, ads, creatives, and targeting by default (`deepCopy: true`). The copy is created paused so callers can review before launching. Per-platform implementation: - **Meta** uses the native `POST /{campaign-id}/copies` endpoint. - **TikTok** has no native copy primitive; Zernio walks the source graph (`/v2/campaign/get/`, `/v2/adgroup/get/`, `/v2/ad/get/`) and recreates each entity via the corresponding `/create/` endpoints, carrying over budget / targeting / bid_type / bid_price / deep_bid_type / creative fields. Spark Ad linkage (`tiktok_item_id`) is preserved. - **LinkedIn** has no native copy primitive; Zernio walks the source CampaignGroup → Campaigns → Creatives and recreates each entity, carrying over `type` / `costType` / `unitCost` / `optimizationTargetType` / `creativeSelection` / `objectiveType` / `format` / `dailyBudget` / `totalBudget` / `targetingCriteria` / `runSchedule` and every Creative's `content` object verbatim. `statusOption: INHERITED_FROM_SOURCE` is evaluated **per entity**: any Group / Campaign / Creative whose source is `ACTIVE` gets its clone activated too. Duplicating an ACTIVE campaign with `INHERITED_FROM_SOURCE` starts a second front of spend the moment the clone activates — the safe default is `PAUSED`. The new hierarchy is asynchronous to materialize in our DB — we trigger sync discovery automatically. Set `syncAfter: false` to skip and poll `/v1/ads/tree` on your own cadence. Other platforms return 501 Not Implemented.