Zernio
Zernio
API Reference

Campaigns and Ads

Overview

Get campaign treeGETGet daily account metricsGET

Campaigns

List campaignsGETCreate a standalone campaignPOSTUpdate a campaignPUTDuplicate a campaignPOSTDelete a campaignDELETEPause or resume a campaignPUTPause or resume many campaignsPOSTAttach extension assets to a Google Search campaignPOST

Ad Sets

Live ad-set details incl. learning phaseGETUpdate an ad setPUTDuplicate an ad setPOSTDelete an ad setDELETEPause or resume a single ad setPUTList Search keywordsGET

Ads

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

Other

Add Search keywords to an ad groupPOSTCreate a standalone ad groupPOSTCreate a Google Ads portfolio bid strategyPOSTRead a campaign's current biddingGETRead a Google campaign's device, location, and language targetingGETList ad setsGETList Google Ads portfolio bid strategiesGETList campaign-level negative keywordsGETRemove a Search keywordDELETEReplace campaign-level negative keywordsPUTPause or enable a Search keywordPATCHUpdate a Google Ads portfolio bid strategyPATCHEdit a Google campaign's device, location, or language targetingPUT
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Campaigns and Ads

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


PlatformsMeta
POST
/v1/ads/boost

Authorization

bearerAuth
AuthorizationBearer <token>

API key authentication - use your Zernio API key as a Bearer token

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.

Lengthlength <= 255

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

{  "ad": {    "_id": "string",    "name": "string",    "platform": "facebook",    "status": "active",    "configuredStatus": "string",    "reviewStatus": "in_review",    "adType": "boost",    "creativeType": "carousel",    "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,      "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,      "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": {      "thumbnailUrl": "string",      "imageUrl": "string",      "videoId": "string",      "videoUrl": "string",      "creativeId": "string",      "objectType": "string",      "objectStoryId": "string",      "effectiveObjectStoryId": "string",      "pageId": "string",      "effectiveInstagramMediaId": "string",      "instagramUserId": "string",      "instagramPermalinkUrl": "string",      "mediaUrls": [        "string"      ],      "isServing": true,      "servingHoldReasons": [        "string"      ],      "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"}
Was this page helpful?

Create standalone ad

Creates a paid ad with custom creative across Meta, Google Ads, Pinterest, TikTok, X/Twitter, LinkedIn, and OpenAI Ads (ChatGPT Ads). Supports three mutually-exclusive request shapes selected by the body, a legacy single-creative shape (all platforms, default), a Meta-only multi-creative shape via the creatives array (one ad set with N ads sharing budget and targeting), and an attach shape via adSetId that adds one new ad to an existing ad set, inheriting its budget, targeting, and schedule (Meta, TikTok, and LinkedIn; on LinkedIn adSetId is the existing Campaign id, and the budget, schedule, targeting and bidding fields must be omitted). Per-platform required fields, budget minimums, and video-ad rules are documented on each property below. 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`. **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). Keys are scoped to your credential and expire after 24h.

Update ad

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, 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`, and DEVICE bid adjustments via `targeting.devices` — each list you send becomes the FULL new set of its kind (criteria not in the list are removed); a kind left out is untouched. Any other `targeting` field returns 400: Google cannot mutate broad targeting post-create without recreating the campaign. `creative` returns 501. - **LinkedIn**: status, budget, targeting (geo countries only, applied to the LinkedIn Campaign via PARTIAL_UPDATE), 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 `unsupported_platform_operation`. OpenAI Ads budget is lifetime-only (see `budget.type` below). **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.

postId?string

Zernio post ID (provide this or platformPostId)

platformPostId?string

Platform post ID (alternative to postId)

accountId*string

Social account ID

adAccountId*string

Platform ad account ID

name*string
Lengthlength <= 255
goal*string

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

Value in

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

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.

budget?

Required unless adSetId is set.

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. Lead ads force ON_AD and ignore this.

Value in

  • "INSTAGRAM_PROFILE"
  • "WEBSITE"
  • "ON_AD"
  • "MESSENGER"
  • "WHATSAPP"
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
schedule?
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).

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.

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

tracking?

Meta only. 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. BRAZIL_REGULATION, SINGAPORE_UNIVERSAL, TAIWAN_UNIVERSAL, THAILAND_UNIVERSAL, AUSTRALIA_FINSERV, INDIA_FINSERV, TAIWAN_FINSERV). Forwarded to the ad set.

regionalRegulationIdentities?

Meta only. Beneficiary/payer entity IDs for regionalRegulatedCategories. Values are numeric IDs from Meta verification. Keys vary by category (e.g. universal_beneficiary / universal_payer for BRAZIL_REGULATION and THAILAND_UNIVERSAL). If omitted, Meta uses Ads Manager defaults when configured.

linkUrl?string

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.

Formaturi
callToAction?string

CTA 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: the CTA enum of POST /v1/ads/create plus VIEW_INSTAGRAM_PROFILE, which is accepted on boost only. For that value linkUrl is typically the Instagram profile URL.

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

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.

status?string

Meta, TikTok, and LinkedIn. Publish state of the created entities. Omitted or ACTIVE publishes live (default); PAUSED creates them paused so you can review before they spend. On LinkedIn the whole campaign group, campaign, and creative hierarchy stays PAUSED (intendedStatus PAUSED on each).

Value in

  • "ACTIVE"
  • "PAUSED"
optimizationGoal?string

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