Zernio
APIFor you
Zernio
APIFor you
API Reference

Campaigns and Ads

Overview

Get campaign treeGETGet daily account metricsGET

Campaigns

List campaignsGETCreate a standalone campaignPOSTGet live campaign detailsGETUpdate a campaignPUTDuplicate a campaignPOSTDelete a campaignDELETEPause or resume a campaignPUTPause or resume many campaignsPOSTList campaign assetsGETAttach campaign assetsPOSTUpdate campaign assetsPUTRemove campaign assetsDELETERead a Google campaign's device, location, excluded location, and language targetingGETEdit a Google campaign's device, location, excluded location, or language targetingPUTRead a campaign's ad schedule (dayparting)GETReplace a campaign's ad schedule (dayparting)PUTGet campaign conversion goalsGETUpdate campaign conversion goalsPATCH

Asset Groups

List Performance Max asset groupsGETCreate a Performance Max asset groupPOSTGet a Performance Max asset groupGETUpdate a Performance Max asset groupPATCHRemove a Performance Max asset groupDELETELink or unlink asset group assetsPOSTReplace an asset group's listing-group treePUT

Ad Sets

List ad setsGETCreate a standalone ad groupPOSTGet live ad-set detailsGETUpdate an ad setPUTDuplicate an ad setPOSTDelete an ad setDELETEPause or resume a single ad setPUTList ad-group assetsGETAttach ad-group assetsPOSTUpdate ad-group assetsPUTRemove ad-group assetsDELETE

Ads

List adsGETGet ad detailsGETCreate standalone adPOSTBoost post as adPOSTUpdate adPUTDuplicate an adPOSTCancel an adDELETEPause or resume a single adPUT

Keywords

List Search keywordsGETAdd Search ad-group keywordsPOSTPause or enable a Search keywordPATCHRemove a Search keywordDELETEList campaign-level negative keywordsGETReplace campaign-level negative keywordsPUTList campaign negative listsGETReplace campaign negative listsPUT

Bidding

Read a campaign's current biddingGETList portfolio bid strategiesGETCreate portfolio bid strategyPOSTUpdate portfolio bid strategyPATCH

Recommendations

List Google Ads recommendationsGETApply Google Ads recommendationsPOSTDismiss Google Ads recommendationsPOST

Other

Create a shared budgetPOSTRead the platform's review verdict for an adGETReview status of each creative in a TikTok Smart+ adGETList shared budgetsGETReview status of every TikTok Smart+ ad of an advertiserGET
Dashboard
OverviewPlatformsAPI ReferenceResources
Campaigns and Ads

Boost post as ad

Turns an already published post into a paid ad that keeps the post's likes and comments, creating the campaign around it.

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, whatsappPhoneNumber and adSetId are Meta-only and return 400 on other platforms.

accountId may be a Facebook, Instagram or Meta ads (business login) connection. A business-login connection has no posting account, so pass the post as platformPostId (Facebook pageId_postId or an Instagram media id); a Zernio postId is a 400 there.

For X, accountId may be the posting X account or its X Ads connection: either resolves the other on the same profile. A Zernio postId on an X Ads connection resolves through its X account, and is a 400 when the profile has none (pass platformPostId instead).

Reddit. accountId is the Reddit Ads connection (platform redditads). Pass the Reddit post as platformPostId (t3_abc123 or the bare id), or a Zernio postId, which resolves through the Reddit account connected on the same profile (400 when there is none). Reddit promotes only posts made by a profile of the ad account: a post by any other author returns 422. Goals are traffic, awareness, conversions, video_views (the post must be a video, 422 otherwise) and lead_generation; conversions and lead_generation need a pixel that already received the event (see promotedObject). leadGenFormId returns 400: Reddit retired its native lead forms, so lead_generation sends people to linkUrl and optimizes toward the pixel's Lead event. Each boost creates a campaign, an ad group carrying the budget, the targeting and the bid, and the ad. bidStrategy sets the ad group's bid: LOWEST_COST_WITHOUT_CAP (or omitted) is automatic bidding (BIDLESS), LOWEST_COST_WITH_BID_CAP is MANUAL_BIDDING with bidAmount as the bid, COST_CAP is MAXIMIZE_VOLUME with bidAmount as the cap; any other strategy returns 422 before anything is created.

Messaging boosts (Meta). Use goal: engagement with callToAction: WHATSAPP_MESSAGE, MESSAGE_PAGE, or INSTAGRAM_MESSAGE. The CTA implies WHATSAPP, MESSENGER, or INSTAGRAM_DIRECT respectively; destinationType alone does not select a messaging CTA. Omit linkUrl only for messaging CTAs. Plain link CTAs keep their goal and link behavior when combined with an independent destinationType. The campaign uses OUTCOME_ENGAGEMENT and the ad set uses CONVERSATIONS with the promoted Page. Optional whatsappPhoneNumber selects a number already paired with that Page. Conflicting CTA/destination, instant form, goal, or optimizationGoal inputs return 400. Attach requires the target ad set destination to match. Existing post references preserve social proof; an Instagram reel rejected by Meta is not re-uploaded as a new post for a messaging boost.

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).


PlatformsMetaGoogleTikTokLinkedInPinterestX
POST
/v1/ads/boost

Authorization

bearerAuth
AuthorizationBearer <token>

API key authentication: send your Zernio API key in the Authorization header, prefixed with Bearer.

In: header

Header Parameters

Idempotency-Key?string

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. A request cut off mid-handler (for example by a function timeout) can leave its key in progress, answering 409 until the key expires after 24 hours.

Lengthlength <= 255

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

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",    "profileId": "string",    "status": "active",    "configuredStatus": "string",    "reviewStatus": "in_review",    "adType": "boost",    "creativeType": "carousel",    "goal": "engagement",    "isExternal": true,    "budget": {      "amount": 0,      "type": "daily",      "daily": 0,      "lifetime": 0,      "pacing": "string"    },    "metrics": {      "spend": 0,      "impressions": 0,      "reach": 0,      "clicks": 0,      "ctr": 0,      "cpc": 0,      "cpm": 0,      "engagement": 0,      "conversions": 0,      "allConversions": 0,      "costPerConversion": 0,      "actions": {        "property1": 0,        "property2": 0      },      "actionValues": {        "property1": 0,        "property2": 0      },      "purchaseValue": 0,      "roas": 0,      "costPerAction": {        "property1": 0,        "property2": 0      },      "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,      "video2SecWatchedActions": 0,      "video6SecWatchedActions": 0,      "video6SecFocusedViews": 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": "string",    "optimizationGoal": "string",    "costType": "string",    "servingStatuses": [      "string"    ],    "platformAdAccountName": "string",    "platformCreatedAt": "2019-08-24T14:15:22Z",    "bidStrategy": "LOWEST_COST_WITHOUT_CAP",    "bidAmount": 0,    "roasAverageFloor": 0,    "promotedObject": {      "custom_event_type": "string",      "pixel_id": "string",      "page_id": "string",      "application_id": "string",      "product_set_id": "string"    },    "creative": {      "assetGroup": {        "name": "string",        "finalUrl": "http://example.com",        "headlines": [          "string",          "string",          "string"        ],        "longHeadline": "string",        "descriptions": [          "string",          "string"        ],        "businessName": "string",        "images": {          "landscape": [            "http://example.com"          ],          "square": [            "http://example.com"          ],          "logo": [            "http://example.com"          ]        },        "youtubeVideoId": "string"      },      "assetGroupResourceName": "string",      "headlines": [        {          "text": "string",          "pinnedField": "HEADLINE_1"        },        {          "text": "string",          "pinnedField": "HEADLINE_1"        },        {          "text": "string",          "pinnedField": "HEADLINE_1"        }      ],      "descriptions": [        {          "text": "string",          "pinnedField": "DESCRIPTION_1"        },        {          "text": "string",          "pinnedField": "DESCRIPTION_1"        }      ],      "finalUrls": [        "http://example.com"      ],      "thumbnailUrl": "string",      "imageUrl": "string",      "assetResourceName": "string",      "assets": {        "marketingImages": [          {            "resourceName": "string"          }        ],        "squareMarketingImages": [          {            "resourceName": "string"          }        ]      },      "videoId": "string",      "videoUrl": "string",      "creativeId": "string",      "creativeFeatures": {        "property1": "OPT_IN",        "property2": "OPT_IN"      },      "catalog": {        "productSetId": "string",        "format": "single_image",        "formatTransformationSpec": [          {            "format": "string",            "data_source": [              "string"            ]          }        ]      },      "objectType": "string",      "objectStoryId": "string",      "effectiveObjectStoryId": "string",      "pageId": "string",      "effectiveInstagramMediaId": "string",      "instagramUserId": "string",      "instagramPermalinkUrl": "string",      "mediaUrls": [        "string"      ],      "carouselCards": [        {          "imageUrl": "string",          "imageHash": "string",          "headline": "string",          "description": "string",          "linkUrl": "string",          "callToAction": "string",          "videoId": "string"        }      ],      "isServing": true,      "adFormat": "string",      "servingHoldReasons": [        "string"      ],      "body": "string",      "googleHeadline": "string",      "googleDescription": "string",      "youtubeVideoIds": [        "string"      ],      "linkUrl": "string",      "whatsappPhoneNumber": "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"}
Was this page helpful?

Create standalone ad

Creates a paid ad with your own creative, building the campaign and ad set it needs or adding it to existing ones. Create a paid ad with custom creative across Meta, Google Ads, Pinterest, TikTok, X, LinkedIn, OpenAI Ads (ChatGPT Ads) and Reddit Ads. Google Performance Max: set `campaignType: "pmax"` and supply `assetGroup` with text, images by role, business name and finalUrl. Creates a daily budget, PAUSED campaign and asset group atomically. `validateOnly: true` validates the complete request with Google without creating or persisting resources. Read assets with `GET /v1/ads/campaigns/{campaignId}/asset-groups`. The logo is required; video is optional via `assetGroup.youtubeVideoId`. Brand guidelines are disabled at creation. All supplied asset links are validated together against Google's minimum asset requirements. PMax rejects ACTIVE creation, portfolio bidding, bid caps, legacy creative fields and attach shapes. Geo and language targeting are supported; omitted geo targets all locations. PMax does not require top-level goal, headline, body or linkUrl. Supported bidding: omitted or LOWEST_COST_WITHOUT_CAP for Maximize Conversions, COST_CAP plus bidAmount for target CPA, LOWEST_COST_WITH_MIN_ROAS plus roasAverageFloor for Maximize Conversion Value with target ROAS. Google Demand Gen: set `campaignType: "demand_gen"` and supply `demandGen`. Creates a daily budget, PAUSED campaign, one ad group and one ad in a single atomic request: a multi-asset image ad, a video responsive ad when `demandGen.youtubeVideoIds` is sent, or a carousel ad (2 to 10 cards) when `demandGen.carouselCards` is sent. Geo (countries, regions, cities, zips, metros) and languages go on the ad group, as Demand Gen requires. `demandGen.channels` sets the ad group's channel controls and `demandGen.audience` builds a Google Audience (user lists, interests, custom audiences, age ranges, genders) attached to the ad group, or `demandGen.audienceId` attaches an existing one. `validateOnly: true` validates everything with Google without creating resources. Supported bidding: omitted or LOWEST_COST_WITHOUT_CAP for Maximize Conversions, COST_CAP plus bidAmount for target CPA, LOWEST_COST_WITH_MIN_ROAS plus roasAverageFloor for target ROAS, LOWEST_COST_WITH_BID_CAP plus bidAmount for target CPC. The created ad carries the native `platformCampaignId`, `platformAdSetId` (ad group) and `platformAdId`. Activate with PUT /v1/ads/campaigns/{campaignId}/status; budget, bidding and name are edited with the regular campaign endpoints; creative, channels, audience and ad group targeting with PUT /v1/ads/{adId} (`demandGen`, `targeting`). To grow an existing Demand Gen campaign, send `existingCampaignId` (adds a PAUSED ad group, with its own geo, languages, channels and audience, plus its ad) or `adSetId` (adds a PAUSED ad to that ad group); neither takes budget, bidding or schedule fields, and both support `validateOnly`. Other mutually-exclusive request shapes are selected by the body: - Legacy single-creative shape (all platforms, the default). - Meta-only multi-creative shape via the creatives array: one ad set with N ads sharing budget and targeting. - Attach shape via adSetId: adds one new ad to an existing ad set, inheriting its budget, targeting, and schedule (Meta, Google Ads, TikTok, and LinkedIn). On LinkedIn adSetId is the existing Campaign id, and the budget, schedule, targeting and bidding fields must be omitted. On LinkedIn `goal` may be omitted too (taken from the Campaign's objective), and the created ad's `targeting` echoes the Campaign's audience. Meta accepts `creativeFeatures` on the single and attach shapes and as defaults for `creatives[]`; an item replaces the whole feature map. `promotion` is not supported on any shape and any object is rejected with 400. Reusing `existingCreativeId` uses the existing creative settings instead of new settings. Requested settings are persisted for lists, exports, and default ad-detail reads. Per-platform required fields, budget minimums, and video-ad rules are documented on each property below. **Meta required fields per shape** (a missing one is a 400 naming it in `param`): - Every shape that builds a new ad set (no `adSetId`, `existingCampaignId` or `buyingType: "RESERVED"`): `budgetAmount` and `budgetType`, plus `endDate` for a lifetime budget. - Single image or video (the default), also with `adSetId`: `headline` (max 255), `body`, `callToAction`, exactly one of `imageUrl` / `video` (none for a catalog template creative, which ignores and does not upload any sent), and `linkUrl` unless the goal is `lead_generation` or `page_visits` or `leadGenFormId` is set. - `bodies[]` / `headlines[]` text variations: `body`, one list with 2+ items, `callToAction`, `imageUrl` or `video`, and `linkUrl` unless the goal is `lead_generation` or `leadGenFormId` is set. `headline` is not required. - `placementAssets`: `headline`, `body`, `callToAction`, and `linkUrl` with the same exemptions as the single shape; the media comes from `placementAssets`. - `carouselCards`: `body`, `callToAction`, and `linkUrl` unless the goal is `lead_generation` or `leadGenFormId` is set. `headline` is not required. - `creatives[]`: per entry `headline`, `body`, `callToAction`, `imageUrl` or `video`, and `linkUrl` unless the goal is `lead_generation`. Top-level `body` is not required. - `dynamicCreative`: `imageUrls` or `videoUrls`, `callToActionTypes`, and `linkUrls` unless the goal is `lead_generation`. Top-level `body` is not required. - `existingCreativeId`: no creative fields. Goal `page_likes`: `body` only. - Goal `lead_generation` always needs `leadGenFormId`, except with `existingCreativeId`. LinkedIn creates a Single Image or Single Video Ad backed by a Direct Sponsored Content "dark post" authored by a Company Page (see `organizationId`). Supported goals are engagement, traffic, awareness, and video_views (video ads use the `video` field; video_views requires a video), and traffic ads require `linkUrl`. Reddit Ads (`accountId` of a `redditads` connection, `adAccountId` like `a2_abc123`) creates a campaign, an ad group carrying the budget, schedule, targeting and pixel, and an ad running a new promoted post authored by the ad account's first profile (422 when it has none). The creative format follows the body: `carouselCards` builds a carousel, `video` a video ad, `imageUrl` an image ad, and none of them a text ad, which cannot link out (`linkUrl` is a 400 there; `body` is required). Only the legacy single-creative shape is available: `creatives[]`, `adSetId`, `existingCampaignId` and `validateOnly` are refused. Reddit throttling surfaces as 429 `rate_limited` with `platform: reddit`; retry after a delay. To promote an existing Reddit post use POST /v1/ads/boost. **Idempotency:** this endpoint is not idempotent at the platform level (a blind retry creates a second campaign/ad set/ad). Send an `Idempotency-Key` header to make retries safe: the first request with a given key creates the ad and we store the response; a retry with the same key replays that exact response (with `Idempotent-Replayed: true`) instead of creating duplicates. Reusing a key with a different body returns 422; a key whose first request is still in flight returns 409 (retry after a short backoff). If the first request is cut off mid-create (for example by a function timeout), its key can stay in progress and keep returning 409 until it expires; use a new key to retry sooner, after checking whether the ad was created. Keys are scoped to your credential and expire after 24h.

Update ad

Changes an ad's status, budget, targeting or creative and applies the change on the platform. Patch one or more fields on an ad. Status, budget, targeting, and creative changes are propagated to the platform. Per-platform support: - **Meta** (Facebook + Instagram): all fields supported. - **TikTok**: status, budget, `name` (renames the ad), targeting (via `/v2/adgroup/update/`), and creative (via `/v2/ad/update/` patch-style: `headline` is ignored, `body` becomes `ad_text`). - **Google**: status, budget, KEYWORD edits via `targeting.keywords` / `targeting.negativeKeywords`, DEVICE bid adjustments via `targeting.devices`, LOCATION edits via `targeting.locations` (or the equivalent top-level `targeting.countries` / `regions` / `cities` / `zips` / `metros`), and LANGUAGE edits via `targeting.languages`. Each list you send becomes the FULL new set of its kind (criteria not in the list are removed, except devices, which Google cannot remove and which are switched off with a bid modifier of 0 instead); a kind left out is untouched. Any other `targeting` field returns 400: Google cannot mutate it post-create without recreating the campaign. Creative edits are dispatched on the ad's `advertisingChannelType`, and every supported field replaces a whole set; a field you omit is preserved. - **Search**: top-level `headlines`, `descriptions` and `finalUrls`. Use 3-15 headlines (1-30 characters) and 2-4 descriptions (1-90 characters). Omit an asset to remove it; omit pinnedField on an included asset to unpin it. Updates do not pad or truncate text. The legacy creative fields remain unsupported. - **Display**: top-level `headlines` (1-5, no pinnedField, display ads have no pinned positions), `descriptions` (1-5) and `finalUrls`, plus `creative.longHeadline`, `creative.businessName`, `creative.imageUrl` (the landscape marketing image) and `creative.squareImageUrl`. Each image URL is uploaded as a new Google asset and the ad is pointed at it; Google assets are immutable, so the previous asset stays in the account's asset library. A new landscape image also updates `creative.assetResourceName` to the new asset, and each replaced image updates its own role in `creative.assets`. - **Performance Max**: top-level `assetGroup`, which swaps asset roles on the ad's asset group. The other creative fields return 422 for this channel, and `assetGroup` returns 422 on any other channel. - **Demand Gen**: top-level `demandGen` (see GoogleDemandGenUpdate): creative, channel and audience changes in one atomic Google request. The other creative fields return 422 for this channel. `targeting` takes locations, languages, `locationTargetingType` and `devices`; locations and languages are written to the ad's ad group, which Demand Gen requires (campaigns migrated from Discovery that still target on the campaign keep being written there), so they apply to every ad in that ad group. - **LinkedIn**: status, budget, targeting (countries or regions, excludedLocations (countries), the B2B facets, and audience segments; applied to the LinkedIn Campaign via PARTIAL_UPDATE, and REPLACES the campaign's entire targetingCriteria, not a merge), and creative (uploads new media, creates a replacement inline creative on the same campaign, pauses the old one). - **Pinterest / X / OpenAI Ads**: status + budget only. Sending `targeting` or `creative` returns 501 with code `feature_not_available`. OpenAI Ads budget is the campaign's spend cap, daily or lifetime (see `budget.type` below). - **Reddit Ads**: status, budget, `name` (renames the ad) and targeting. `budget` goes to the ad's ad group, or to its campaign when the ad group spends a campaign budget, and keeps its daily or lifetime type (422 to switch). `targeting` REPLACES the ad group's whole targeting (geo, communities, conversation keywords, interests, audiences, languages, gender), so it applies to every ad in the ad group. `creative` returns 501: a Reddit ad runs a post, so create a new ad with the new creative instead. **Google location and language replacement:** locations, languages and devices are campaign-level criteria on Google, so these edits apply to every ad group and ad in the ad's campaign. Send the complete list you want to keep. Zernio diffs it against the campaign's live criteria and sends the removes and the creates in ONE `googleAds:mutate`, so the campaign is never left with a half-applied set; criteria already in the list keep their criterion ID and history. Excluded (negative) locations are left untouched. An empty location list returns 400 (a Google campaign with no location criteria targets every country, which is never what "remove my locations" means, so omit the field instead). Send either `targeting.locations` or the top-level geo fields, not both: mixing them returns 400. **Google radius targeting:** `customLocations` is editable and is replaced the same way, but as its OWN set. Google models a place (LOCATION) and a point plus radius (PROXIMITY) as different criterion types, so the two are independent: sending `customLocations` replaces every radius and leaves the cities and countries alone, and sending places replaces those and leaves the radius alone. Send `customLocations: []` to drop radius targeting entirely. A circle you re-send unchanged keeps its criterion ID rather than being removed and recreated. **Google keyword replacement:** These edits affect the ad's entire ad group, including sibling ads. Positive (`targeting.keywords`) and negative (`targeting.negativeKeywords`) sets are independent: omit a field to leave that set unchanged, or send `[]` to remove every keyword of that kind. Zernio compares each supplied set with Google's live criteria by case-insensitive keyword text and match type. A matching criterion is left untouched, retaining its criterion ID, enabled/paused status, keyword-level bid overrides, labels, and criterion-associated history/statistics. Zernio does not reset its quality score; Google continues to calculate scores and statistics normally. Text comparison does not trim whitespace. A bare string or an object without `matchType` means `broad`, not the existing criterion's match type. For example, resending an existing `{ "text": "plumber", "matchType": "exact" }` preserves it; sending `"plumber"` instead removes that EXACT criterion and requests a BROAD one. Changing text or match type removes criteria no longer requested and creates any missing criteria. New criteria get new IDs and do not inherit removed criteria's bid overrides, labels, or history. Historical reporting for a removed criterion is not transferred to its replacement. To add keywords without replacing a set, use [POST /v1/ads/keywords](https://docs.zernio.com/ad-campaigns/add-ad-keywords). Use `PATCH /v1/ads/keywords/{keywordId}` to pause/enable one keyword, or `DELETE /v1/ads/keywords/{keywordId}` to remove it.

creativeFeatures?

Meta Advantage+ creative enhancements. Map snake_case feature names to OPT_IN or OPT_OUT; Meta validates supported keys and unspecified features default to OPT_OUT. auto_promotion_tag is an Advantage+ enhancement, not the Ads Manager Promotion setting. The deprecated standard_enhancements bundle is rejected by Meta.

postId?string

Zernio post ID (provide this or platformPostId)

platformPostId?string

Platform post ID (alternative to postId). Reddit: the post id, with or without the t3_ prefix. Meta: a Page post id (<pageId>_<postId> or the bare post id) or an Instagram media id. On a Meta Ads business-login connection (platform metaads) the id is resolved live: an Instagram media id is boosted as that media, running as the Instagram account that owns it.

accountId*string

Zernio account id. Normally the connected posting account (facebook, instagram, tiktok, linkedin, pinterest, twitter), a googleads account or a redditads account. TikTok: the TikTok Ads connection (platform tiktokads) is accepted too when the post brings its own authorization (sparkAuthCode or sparkPosts), so Spark ads need no organic TikTok account connected; such a call must use platformPostId, not postId.

adAccountId*string

Platform ad account ID

name*string
Lengthlength <= 255
campaignName?string

Exact name for the campaign this boost provisions. Omitted keeps the default <name> - Campaign. Every platform: on LinkedIn it names the campaign group. Ignored on the Meta attach shape (adSetId), which creates no campaign.

Lengthlength <= 255
adSetName?string

Exact name for the ad-group level this boost provisions. Omitted keeps the default <name> - Ad Group. Meta: ad set; TikTok, Pinterest, Google: ad group; X: line item; LinkedIn: the campaign under the campaign group. Ignored on the Meta attach shape.

Lengthlength <= 255
goal*string

Available goals vary by platform. Meta (Facebook/Instagram) and TikTok support all 7. LinkedIn supports all except app_promotion. X supports engagement, traffic, awareness, video_views, app_promotion. Pinterest and Google Ads support only engagement, traffic, awareness, video_views. Reddit supports traffic, awareness, video_views, lead_generation and conversions.

Value in

  • "engagement"
  • "traffic"
  • "awareness"
  • "video_views"
  • "lead_generation"
  • "conversions"
  • "app_promotion"
adSetId?string

Meta, or TikTok with smartPlus: true. Attach the boosted post to this existing ad set instead of creating a campaign. On TikTok the id is an existing Smart+ ad group: the post is added as one more Spark ad in it (up to 30 per ad group), under the identity its sparkAuthCode creates; goal and budget are inherited from the Smart+ campaign; a regular ad group is rejected with a 400. Meta: The ad set then owns budget, schedule and targeting; sending those too is a 400.

existingCampaignId?string

TikTok only. Create the ad group and the Spark ad under this existing TikTok campaign instead of creating a new campaign. The campaign keeps its own status and objective (the objective must fit goal). Cannot be combined with adSetId or smartPlus. On Meta use POST /v1/ads/create with existingCampaignId.

identityId?string

TikTok only. The identity the ad runs as (the profile shown on the ad), from GET /v1/ads/tiktok-identities. Default: the connected TikTok account's own identity. Must be authorized on the advertiser or the call fails naming the available ones.

identityType?string

TikTok only. Type of identityId; resolved from the advertiser's identity list when omitted.

Value in

  • "TT_USER"
  • "CUSTOMIZED_USER"
  • "BC_AUTH_TT"
budgetAmount?number

Budget in whole currency units, the same flat field as POST /v1/ads/create. Required unless adSetId is set. Minimum varies: TikTok=$20, Pinterest=$5, others=$1

budgetType?string

Goes together with budgetAmount. lifetime requires schedule.endDate.

Value in

  • "daily"
  • "lifetime"
lifetimeBudgetCap?number

X only. A lifetime total, in whole currency units, that caps a daily budget (budgetType daily): the line item gets both daily_budget_amount_local_micro and total_budget_amount_local_micro, and stops spending once the total is reached. Must be at least budgetAmount. Read back as budget.daily and budget.lifetime. 400 on a lifetime budget or on another platform.

Range0 < value
budget?
Deprecated

Alias of budgetAmount + budgetType, kept for existing callers

instagramAccountId?string

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.

destinationType?string

Meta only. Ad-set destination_type: where the click LANDS, as opposed to instagramAccountId which is who the ad runs as. Independent of plain link CTAs and their goal. A messaging callToAction selects its destination automatically; an explicit destinationType must then match. Lead ads use ON_AD.

Value in

  • "INSTAGRAM_PROFILE"
  • "WEBSITE"
  • "ON_AD"
  • "MESSENGER"
  • "WHATSAPP"
  • "INSTAGRAM_DIRECT"
whatsappPhoneNumber?string

Meta WhatsApp only. E.164 number already paired with the Page. Omit to use the default pairing. Requires WHATSAPP_MESSAGE callToAction. Stored as creative.whatsappPhoneNumber on the ad.

Match^\+[1-9]\d{6,14}$
workflowId?string

Meta messaging boosts only (callToAction MESSAGE_PAGE, WHATSAPP_MESSAGE or INSTAGRAM_MESSAGE). A workflow in the account's profile, started in the conversation a click on the ad opens. Stored on the ad. 400 without a messaging callToAction or on another platform, 404 when no such workflow exists in the profile.

Match^[a-fA-F0-9]{24}$
currency?string

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).

Length3 <= length <= 3
startDate?string

Ad-set start time (ISO 8601, e.g. "2026-06-10T09:00:00Z"), mapped to the ad set's start_time. When omitted the ad starts delivering immediately. On Meta, TikTok, X, Pinterest and Reddit a value without an offset (YYYY-MM-DD, YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS) is read in the ad account timezone. Same field as on POST /v1/ads/create.

Formatdate-time
endDate?string

Ad-set end time (ISO 8601), mapped to the ad set's end_time (on X, the line item's end_time; X campaigns carry no flight dates). Required for lifetime budgets. On Meta, TikTok, X, Pinterest and Reddit a value without an offset (YYYY-MM-DD, YYYY-MM-DD HH:MM:SS or YYYY-MM-DDTHH:MM:SS) is read in the ad account timezone, and a date-only end runs to 23:59:59 local. Same field as on POST /v1/ads/create.

Formatdate-time
schedule?
Deprecated

Alias of the top-level startDate / endDate, kept for existing callers. Sending both forms with differing values is a 400.

targeting?

Same 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). On Reddit the whole TargetingSpec applies, including communities, excludedCommunities, conversationKeywords, excludedConversationKeywords, interests, audienceInclude / audienceExclude and excludedLocations; without any geo the ad targets the US.

locationTargetingType?string

Google only (400 elsewhere). Written on the campaign the boost creates.

Value in

  • "presence"
  • "presence_or_interest"
rawTargeting?

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.

bidStrategy?string
Deprecated

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.

Meta bid strategy applied to the ad set. On TikTok, mapped to bid_type / bid_price / deep_bid_type automatically. On Reddit, applied to the new ad group (see the Reddit section above; 422 for LOWEST_COST_WITH_MIN_ROAS).

Value in

  • "LOWEST_COST_WITHOUT_CAP"
  • "LOWEST_COST_WITH_BID_CAP"
  • "COST_CAP"
  • "LOWEST_COST_WITH_MIN_ROAS"
bidAmount?number
Deprecated

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.

roasAverageFloor?number
Deprecated

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).

platformSpecificData?|

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, which is the preferred home going forward. Sending the bid fields in BOTH places returns a 400 (mutually_exclusive_fields).

tracking?

Meta only, except pixelId, which also picks the Reddit pixel of the new ad group (promotedObject.pixelId wins when both are sent). Tracking specs (pixel, URL tags).

specialAdCategories?array<>

Meta only. Required for housing, employment, credit, or political ads.

specialAdCategoryCountry?array<string>

Meta (metaads) only. 2-letter ISO country codes the special ad category applies to. Requires specialAdCategories to be set (400 otherwise).

regionalRegulatedCategories?array<string>

Meta only. Regional regulation categories required when the ad set targets certain countries (e.g. AUSTRALIA_FINSERV, AUSTRALIA_UNIVERSAL, BRAZIL_REGULATION, INDIA_FINSERV, MALAYSIA_UNIVERSAL, SINGAPORE_UNIVERSAL, TAIWAN_FINSERV, TAIWAN_UNIVERSAL, THAILAND_UNIVERSAL). Forwarded to the ad set.

regionalRegulationIdentities?

Meta only. Verified identity IDs of the beneficiary and payer for each declared regionalRegulatedCategories value. Meta may require them on ad sets created through the API even when a default beneficiary and payer are set in Ads Manager advertising settings (seen on AUSTRALIA_FINSERV), so sending them explicitly is the reliable path. The value is the Verified identity ID (Meta Business Settings > Verified identities), not a Page or Business ID, and beneficiary and payer may be the same ID. Send each ID as a string of digits: Verified identity IDs can have 17 digits, more than a JSON number holds exactly, and a number that arrives rounded is rejected. Numbers up to 9007199254740991 are still accepted. The IDs reach Meta unchanged. Keys by category: AUSTRALIA_FINSERV uses australia_finserv_beneficiary / australia_finserv_payer; TAIWAN_FINSERV uses taiwan_finserv_beneficiary / taiwan_finserv_payer; INDIA_FINSERV uses india_finserv_beneficiary / india_finserv_payer; TAIWAN_UNIVERSAL uses taiwan_universal_beneficiary / taiwan_universal_payer; SINGAPORE_UNIVERSAL uses singapore_universal_beneficiary / singapore_universal_payer; AUSTRALIA_UNIVERSAL, BRAZIL_REGULATION, MALAYSIA_UNIVERSAL and THAILAND_UNIVERSAL use universal_beneficiary / universal_payer. When Meta rejects the ad set for a missing verified advertiser, the error carries param regionalRegulationIdentities, details.expectedIdentityKeys (the keys the declared categories expect), details.suggestedCategories (the markets Meta ties to that subcode) and Meta's metaCode, metaSubcode and fbtraceId. Ignored when adSetId adds the ad to an existing ad set.

linkUrl?string

Website URL for non-messaging CTA buttons. Send it with callToAction. Omit for messaging boosts.

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.

Reddit: the ad's click_url, where a click on the promoted post lands.

Ignored on LinkedIn / Pinterest / X / Google, which infer the destination from the boosted post.

Formaturi
callToAction?string

CTA button label. Non-messaging CTAs require linkUrl. WHATSAPP_MESSAGE, MESSAGE_PAGE, and INSTAGRAM_MESSAGE do not require a URL and reject linkUrl.

Meta: the CTA enum of POST /v1/ads/create plus VIEW_INSTAGRAM_PROFILE, WHATSAPP_MESSAGE, MESSAGE_PAGE, and INSTAGRAM_MESSAGE. VIEW_INSTAGRAM_PROFILE requires linkUrl; the messaging CTAs select their destination automatically.

TikTok: pass-through to call_to_action on the Spark Ad creative; the platform validates the value. See TikTok's "Enumeration - Call-to-Action".

sparkAuthCode?string

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/.

smartPlus?boolean

TikTok only. Run the Spark post in a Smart+ campaign (goal conversions = Smart+ Web Conversions, lead_generation = Smart+ Lead Generation) instead of a regular campaign. Requires sparkAuthCode (the Smart+ ad runs the post under the identity that redeeming its Spark code creates; a Business Center-owned post is not accepted there) and promotedObject.pixelId + customEventType. app_promotion is not available on a Spark post. Rejected with a 400 on other platforms. A Smart+ Spark ad uses a dynamic CTA portfolio, sent as ad_configuration.call_to_action_id (TikTok does not accept a named call to action there): Zernio creates one per ad account and reuses it, and callToAction is rejected with a 400 on this path.

sparkPosts?array<>

TikTok Smart+ only (requires smartPlus: true). Several Spark posts as creatives of ONE Smart+ ad, each with its own post code (TikTok allows 1-50 per ad; posts from different creators mix). Replaces platformPostId + sparkAuthCode. Without adSetId it creates campaign + ad group + one ad carrying all of them; with adSetId it creates one new ad with all of them in that ad group. Rejected with a 400 on other platforms.

Items1 <= items <= 50
promoCodes?array<>

TikTok Smart+ Web Conversions only (requires smartPlus: true, goal conversions). Promo codes or offers TikTok highlights on the ad (Ads Manager's "Add promo code or offer"). A promo code needs shoppers to enter it at checkout; an entry without promoCode is an offer applied automatically. Rejected with a 400 on other platforms and on Lead Generation campaigns.

Items1 <= items <= 10
promotedObject?

Meta, TikTok and Reddit. What the conversion ad set optimizes toward, so a boost of an existing organic post can run for a conversion instead of only engagement or traffic. Required when goal is conversions (Meta also lead_conversion); ignored on goals that do not optimize for a conversion.

Meta: pixelId + customEventType (a commerce event such as PURCHASE under conversions, a leads-class event such as LEAD under lead_conversion), or customConversionId to optimize against a Custom Conversion, or customEventType: OTHER + customEventStr for a pixel custom event. Becomes the ad set promoted_object; without it Meta rejects the ad set ("Please select a promoted object", subcode 1815430). With adSetId the existing ad set already carries it.

TikTok: BOTH pixelId and customEventType are required. TikTok refuses a conversion ad group with no pixel ("Please select a pixel") and one with a pixel but no event ("Select a pixel event."). Combine freely with platformPostId + sparkAuthCode: the pixel lives on the ad group and the Spark item on the creative.

Reddit: every ad group carries a pixel, whatever the goal. pixelId picks it (default: the ad account's first pixel; 422 when the account has none). conversions requires customEventType (PURCHASE, LEAD, COMPLETE_REGISTRATION, SIGN_UP, ADD_TO_CART, PAGE_VISIT, VIEW_CONTENT or CONTENT_VIEW), and the pixel must already have received that event (422 otherwise); lead_generation needs the pixel to have received Lead.

dsaBeneficiary?string

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.

Lengthlength <= 100
dsaPayor?string

Legal 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.

Lengthlength <= 100
leadGenFormId?string

Lead Gen form ID to attach to the boosted ad's creative. REQUIRED when goal is lead_generation. On Meta this is the leadgen_forms ID (create one via POST /v1/ads/lead-forms). On LinkedIn this is the adForm ID (create one via POST /v1/ads/lead-forms with a LinkedIn account); the creative's leadgenCallToAction.destination is set to urn:li:adForm:{id}. Ignored for other goals. Reddit: rejected with 400 on every goal (Reddit retired its native lead forms).

status?string

Publish state of the created entities, on every platform. Omitted or ACTIVE publishes live (default); PAUSED pauses only the top-most object this boost creates and switches everything below it on: a new campaign is held paused with its ad set and ad on (one PUT /v1/ads/campaigns/{campaignId}/status with active brings it live); into an existing campaign (TikTok existingCampaignId) the new ad set is held paused; attached to an existing ad set (adSetId) the new ad itself is paused. On LinkedIn the held campaign group is PAUSED, its campaign and creative ACTIVE. X has no per-ad switch, so its lowest level is the line item. campaignStatus, adSetStatus and adStatus set one level each and always win for that level; status: PAUSED adds a hold of its own only when none of them is PAUSED. To create every object paused, send all three as PAUSED.

Value in

  • "ACTIVE"
  • "PAUSED"
campaignStatus?string

Every platform, same semantics as POST /v1/ads/create. Sets the switch of the new campaign alone (LinkedIn: the campaign group) and overrides status for it. ACTIVE with status: PAUSED switches the campaign on and holds the new ad set paused (its ad on). Omitted, it follows status. Rejected with a 400 alongside adSetId or existingCampaignId, where no campaign is created (change an existing one with PUT /v1/ads/campaigns/{campaignId}/status).

Value in

  • "ACTIVE"
  • "PAUSED"
adSetStatus?string

Every platform, same semantics as POST /v1/ads/create. Sets the switch of the new ad set alone (Google, TikTok and Pinterest: the ad group; LinkedIn: the campaign, which stays DRAFT when held; X: the line item) and overrides status for it. Omitted, it follows status.

Precedence: a level status (campaignStatus, adSetStatus, adStatus) always wins for its level. status: PAUSED then holds the top-most new object that has no level status, and only when no level status is PAUSED; every other new object is switched on. So campaignStatus: ACTIVE + adSetStatus: PAUSED + adStatus: PAUSED keeps the campaign on with the new ad set and ad off, and all three PAUSED create the whole tree paused.

Rejected with a 400 alongside adSetId (that ad set already exists; change it with PUT /v1/ads/ad-sets/{adSetId}/status).

Value in

  • "ACTIVE"
  • "PAUSED"
adStatus?AdActivationStatus

Sets the switch of the new ad alone, also when attaching to an existing ad set with adSetId (Meta, TikTok Smart+), and overrides status for it. Same precedence as adSetStatus. Omitted, it follows status. LinkedIn: a creative held off is created DRAFT, because LinkedIn refuses PAUSED on a creative until its review is APPROVED. A DRAFT creative is not submitted for review and cannot serve; it reads paused, and resuming the ad (PUT /v1/ads/{adId}/status active) switches it ACTIVE, which starts the review.

X returns a 400: a promoted post has no switch of its own, so hold the line item with adSetStatus.

Value in

  • "ACTIVE"
  • "PAUSED"
budgetLevel?string

Meta only, same semantics as POST /v1/ads/create: campaign = Advantage campaign budget (CBO), the budget and bid strategy sit on the campaign and the ad set inherits them. Default adset. Not allowed with adSetId.

Value in

  • "adset"
  • "campaign"
attributionSpec?array<>

Meta only. Ad-set attribution windows, same shape as POST /v1/ads/create. Applied on OUTCOME_SALES, OUTCOME_LEADS and OUTCOME_APP_PROMOTION campaigns (conversions, lead_conversion, lead_generation, app_promotion); other objectives keep Meta's default. Not allowed with adSetId.

Items1 <= items <= 3
bodies?array<string>

Meta only. Extra primary-text options Meta rotates on the boosted post (asset_feed_spec.bodies with DEGREES_OF_FREEDOM); the post keeps its own text as one of the options. Works for Facebook posts and Instagram media. Under a conversions or traffic goal Meta also wants a website URL on the options, taken from linkUrl (send it with a callToAction); engagement boosts need none.

Items1 <= items <= 5
smartTargeting?

TikTok only (a 400 elsewhere). TikTok Smart Targeting on the ad group: audience is TikTok's smart_audience_enabled, interestsBehaviors its smart_interest_behavior_enabled. When on, TikTok may deliver beyond the selected audiences or interests. Available on Video views, Traffic, Lead generation, App install, Web conversion and Community interaction. Only the flags you send are written; an unwritten flag reads back null in nativeSettings, so send false explicitly to be able to verify it is off. Not available with smartPlus or when attaching to an existing ad set (adSetId).

optimizationGoal?string

Meta, or TikTok with goal: video_views. TikTok: ENGAGED_VIEW (6-second Focused View, the default) or ENGAGED_VIEW_FIFTEEN (15-second views), both billed per view (CPV); any other value is a 400. Meta: explicit ad-set optimization_goal override. When omitted, defaults to the value derived from goal. Messaging boosts always use CONVERSATIONS and reject another optimizationGoal. Otherwise 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).