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

Create a standalone campaign

Creates an empty campaign, paused, so you can add ad sets and ads to it afterwards.

Creates a campaign WITHOUT its first ad set / ad, on the platform of the given accountId. Ad sets join it later via existingCampaignId on the create endpoints. Platform notes: on Meta a budget here is campaign-level (CBO) by definition; omit it for ABO (each ad set carries its own budget), and specialAdCategories is Meta-only (400 elsewhere); bidStrategy is Meta and Google (400 elsewhere), and Google also accepts portfolioBidStrategyId instead. Google, X and OpenAI require a budget (422 without one; OpenAI accepts daily or lifetime, Google only budgetType: daily). On OpenAI goal sets the campaign objective, and conversions needs an active standard conversion event on the account. On Reddit goal is one of traffic, awareness, conversions, video_views or lead_generation (422 otherwise) and the budget is optional: with one, the campaign uses Reddit's campaign budget optimization and carries the pixel (see promotedObject, which then needs customEventType for conversions), daily or lifetime (a lifetime budget needs endDate, 400 otherwise). startDate / endDate set the campaign's schedule and need that campaign budget (400 without one). Without a budget, each ad set carries its own budget and dates. LinkedIn creates the campaign GROUP (our campaign level) and rejects a budget, which lives on the campaign (ad set) level there; it comes back status: DRAFT. Created PAUSED (TikTok DISABLE) unless status: ACTIVE where the platform supports it.

Idempotency: send an Idempotency-Key header to make retries safe.


PlatformsMetaGoogleTikTokLinkedInPinterestXOpenAI
POST
/v1/ads/campaigns

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. Only 2xx responses are stored, so a request that failed with a 4xx can be retried with a corrected body under the SAME key.

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

application/json

import Zernio from '@zernio/node';const zernio = new Zernio({ apiKey: process.env.ZERNIO_API_KEY });const { data } = await zernio.adcampaigns.createAdCampaign({  body: {    accountId: 'account_abc123',    adAccountId: 'adaccount_abc123',    name: 'Example',    goal: 'engagement',  },});console.log(data);
{  "validateOnly": true,  "adAccountId": "act_757082720485182",  "campaignId": "",  "objective": "OUTCOME_APP_PROMOTION",  "status": "VALIDATED"}
Was this page helpful?

List campaigns

Lists your campaigns with spend, impressions and clicks summed across their ads, plus each campaign's status and budget. Returns campaigns as virtual aggregations over ad documents grouped by platform campaign ID. Metrics (spend, impressions, clicks, etc.) are summed across all ads in each campaign. Campaign status is derived from child ad statuses (active > pending_review > paused > error > completed > cancelled > rejected). Google campaign budgets include amountMicros, explicitlyShared, resourceName and deliveryMethod after the next successful sync. This endpoint does not fetch Google live. **Status freshness.** `status`, `configuredStatus`, `platformStatus`, `platformAdSetStatus` and `platformCampaignStatus` are the values Zernio last stored. Background sync refreshes them, typically within 15 to 60 minutes (Google up to about 3 hours), and ended or long-paused objects may be refreshed less often. Zernio's own status writes re-read the switches they change. A change made in the platform's own ads manager therefore shows up here only after the next sync. Controllers that act on a switch should pass `live=true`, which reads the switches from the platform now, stores them, and returns `statusReadAt` (null when the read failed and the stored values were returned). With `live=true` (which needs `limit` of 20 or less) each returned campaign's own switch (`platformCampaignStatus`) is read live. Live reads cover TikTok, Meta, Google, OpenAI and Reddit. The rolled-up `status` is not re-derived by a live read. **Budgets here are SYNCED**, never read live, including with `live=true`. To read the current budgets (and status) of every campaign and ad set of a Meta ad account live in one call, for example as a pre-write spend gate, use GET /v1/ads/accounts/live.

Get live campaign details

Shows a Meta campaign's current settings straight from Meta, such as its objective, budget, bid strategy, schedule and delivery issues. Reads one campaign live from Meta, returned verbatim, so a caller that knows a campaign id no longer has to page `GET /v1/ads/campaigns` to find it. The default projection covers name, status, objective, buying type, bid strategy, budgets, spend cap, schedule and `issues_info`. `fields` is a raw-passthrough override; unknown fields return Meta's 400 verbatim. A campaign the resolved connection cannot see comes back as Meta's own 400, not a 404.

accountId*string

Zernio SocialAccount id (posting or ads variant); its platform decides where the campaign is created.

adAccountId*string

Platform ad account id (Meta act_, Google customer id, LinkedIn account id, ...).

name*string
Lengthlength <= 255
goal*string

Mapped to the ODAX objective (same mapping as POST /v1/ads/create).

Value in

  • "engagement"
  • "traffic"
  • "awareness"
  • "video_views"
  • "lead_generation"
  • "lead_conversion"
  • "job_applicants"
  • "conversions"
  • "app_promotion"
  • "catalog_sales"
  • "page_likes"
  • "page_visits"
isSkadnetworkAttribution?boolean

Meta app promotion only. Immutable campaign flag. Set true for iOS 14+ SKAdNetwork campaigns and supply promotedObject.applicationId plus promotedObject.objectStoreUrl. The campaign receives promotedObject only when this flag is true. Cannot be changed on an existing campaign.

promotedObject?

What the ad optimises against. Behaviour depends on the platform.

Meta: forwarded to the ad set's promoted_object (snake-cased). For goal: app_promotion, it is also sent on the campaign only when isSkadnetworkAttribution: true. Plain Android app installs keep the existing campaign payload, with the promoted object only on the ad set. POST /v1/ads/campaigns forwards this object on Meta only for that explicit SKAN flag. Required for goals whose ad-set optimization_goal points at a specific event/page/app (without it Meta rejects the ad-set create with error_subcode: 1815430 "Please select a promoted object for your ad set"):

  • goal: conversions / lead_conversion (OFFSITE_CONVERSIONS): requires pixelId + customEventType, or customConversionId when optimising against a Custom Conversion (the conversion carries its own event definition). For a pixel CUSTOM event (one you named yourself in CAPI/Events Manager), send customEventType: OTHER + customEventStr with the event name.
  • goal: app_promotion (APP_INSTALLS): requires applicationId + objectStoreUrl
  • goal: lead_generation (LEAD_GENERATION): pageId is auto-filled from the connected Page when omitted

Other Meta goals (engagement, traffic, awareness, video_views) ignore this field.

TikTok: used by goal: conversions and the Smart+ goals (smartPlus: true).

  • pixelId maps to the ad group's pixel_id. Required: a TikTok website-conversion ad group without a pixel is rejected with 40002: Please select a pixel.
  • customEventType maps to the ad group's optimization_event (the pixel event to optimise for). Optional on the regular conversions flow, required on Smart+. See the customEventType field below for the valid TikTok codes.
  • applicationId (Smart+ goal: app_promotion only) maps to the ad group's app_id: the App ID of an app registered on the TikTok Ads account (Assets → Events → App Events). Install optimization needs the app's MMP tracking configured.

OpenAI (ChatGPT) Ads: goal: conversions only, on POST /v1/ads/create and POST /v1/ads/campaigns. customEventType picks the conversion event the campaign optimizes for (sent as the campaign's conversion_event_setting_ids); every other promotedObject field is rejected with a 400. See customEventType below.

Reddit Ads: Reddit puts a pixel on every ad group, whatever the goal. pixelId (or tracking.pixelId) picks it; when omitted the ad account's first pixel is used, and an ad account with no pixel returns 422 (create the pixel in Reddit Ads Manager). A pixelId that is not a pixel of the ad account returns 400. goal: conversions requires customEventType (see below). goal: lead_generation optimizes toward the pixel's Lead event. Reddit only optimizes toward an event the pixel has already received: when it never fired, the request returns 422 invalid_resource_state. Fire it from your site or send it with POST /v1/ads/conversions first.

The remaining promotedObject.* fields are Meta-only. Platforms other than Meta, TikTok, OpenAI and Reddit ignore promotedObject entirely.

buyingType?string

Every platform buys at auction by default, so this only needs sending on Meta, and only to choose RESERVED. AUCTION is accepted on every platform and changes nothing. RESERVED (Reach & Frequency) is Meta-only and is rejected with a 400 elsewhere. SKAdNetwork app promotion requires AUCTION.

Value in

  • "AUCTION"
  • "RESERVED"
validateOnly?boolean

Meta and ChatGPT (OpenAI). Runs campaign validation without creating or persisting a campaign; Idempotency-Key storage is bypassed. Returns HTTP 200 with validateOnly true and status VALIDATED. OpenAI has no dry-run, so there Zernio checks the budget, goal and conversion event itself. true on any other platform returns 501 feature_not_available (same as POST /v1/ads/create); false is ignored.

specialAdCategories?array<>
budgetAmount?number

Campaign-level (CBO) budget in WHOLE currency units (USD: 50 = $50.00), NOT cents. Meta's own Marketing API takes this same number in minor units, so it is an easy and expensive mix-up. Requires budgetType.

budgetType?string

Value in

  • "daily"
  • "lifetime"
status?string
Default"PAUSED"

Value in

  • "ACTIVE"
  • "PAUSED"
startDate?string

Reddit only (400 elsewhere). Campaign start (ISO 8601); a value without an offset is read in the ad account time zone. Requires a campaign budget (budgetAmount + budgetType, 400 otherwise). Defaults to now.

Formatdate-time
endDate?string

Reddit only (400 elsewhere). Campaign end (ISO 8601); a value without an offset is read in the ad account time zone. Requires a campaign budget (400 otherwise), and is required for budgetType: lifetime.

Formatdate-time
locationTargetingType?string

Google only (400 elsewhere). Written on the new campaign.

Value in

  • "presence"
  • "presence_or_interest"
bidStrategy?string

Campaign bid strategy. Meta stores bid_strategy alongside the budget, so this REQUIRES budgetAmount + budgetType on the same request; sending it without a campaign budget is a 400. A campaign carrying a strategy without its bid_amount makes every ad set created under it fail with an error that names the ad set (code 100, subcode 1815857), so the bad state is rejected up front rather than accepted. To bid at ad-set level on Meta, set the strategy there instead. On Google: LOWEST_COST_WITHOUT_CAP = Maximize Conversions, COST_CAP + bidAmount = Target CPA, LOWEST_COST_WITH_MIN_ROAS + roasAverageFloor = Target ROAS, LOWEST_COST_WITH_BID_CAP + bidAmount = Maximize Clicks with a CPC ceiling; portfolioBidStrategyId attaches a portfolio strategy instead.

Value in

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

Whole currency units (USD: 5 = $5.00). Required for LOWEST_COST_WITH_BID_CAP and COST_CAP; ignored otherwise. On Meta, validated here but NOT stored: the campaign object has no bid_amount field, only bid_strategy lives on it, and the amount takes effect once an ad set joins this campaign (existingCampaignId on POST /v1/ads/create) and supplies its own bidAmount there. On Google, stored directly on the campaign's bidding strategy.

roasAverageFloor?number

Decimal ROAS multiplier (2.0 = 2.0x). Required for LOWEST_COST_WITH_MIN_ROAS.

portfolioBidStrategyId?string

Google only. Attach an existing portfolio bid strategy (numeric id from GET /v1/ads/bid-strategies) to the new campaign instead of a standard one. Exclusive with bidStrategy.

Match^\d+$
targetImpressionShare?

Google Search only. Target impression share bidding. Exclusive with bidStrategy, portfolioBidStrategyId and manualCpc; bidAmount is refused alongside it (the ceiling is maxCpc).

manualCpc?

Google Search and Display only. Manual CPC bidding. Enhanced CPC is not offered: Google no longer lets Search and Display campaigns enable it. Exclusive with every other strategy field.

networkSettings?

Google Search campaigns only. Which networks the campaign serves on besides Google Search. When omitted at creation, search partners are ON and the Display Network is OFF (the defaults Zernio has always used); on an update only the fields sent change.

Properties1 <= properties