Zernio
Zernio
PlatformsMeta Ads

Build

CampaignsAd SetsCreativesCreative LibraryPreviews

Target

TargetingCustom Audiences

Ad types

Boost a PostCreative TestingConversion CampaignsCatalog AdsMessaging & Call AdsClick-to-WhatsApp AdsLead Gen FormsReach & Frequency

Measure

InsightsMeta PixelsConversions APIAd URL Tracking Tags

Operate

Duplicate & LifecycleAccount & Ops ReadsAd LibraryAd CommentsMedia & Limits
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Meta Ads

Campaigns

Create standalone campaigns, bid strategy, ROAS, and reading the campaign tree


Create the full tree in one call

POST /v1/ads/create builds campaign, ad set and ad together, which is the shape you want almost every time:

const ad = await zernio.adcampaigns.createStandaloneAd({ body: {
  accountId: 'acc_metaads_123',
  adAccountId: 'act_1234567890',
  name: 'Spring sale - US Feed',
  goal: 'traffic',
  budgetAmount: 75,
  budgetType: 'daily',
  headline: 'Spring Sale - 30% off',
  body: 'Limited time. Upgrade today.',
  imageUrl: 'https://cdn.example.com/spring.jpg',
  callToAction: 'SHOP_NOW',
  linkUrl: 'https://example.com/spring',
  countries: ['US'],
  ageMin: 25,
  ageMax: 55,
}});

For goal: "conversions", "lead_conversion", "lead_generation", "app_promotion", or "catalog_sales", see Conversion campaigns and promoted objects — Meta requires a promotedObject (Pixel + event, Page, or App) on the ad set for those optimization goals.

/v1/ads/create uses a flat body (every field at the top level). The /v1/ads/boost endpoint is different, it uses nested budget, schedule, and targeting objects. Don't mix the two shapes. Platform is inferred from accountId, so no platform field in the body.

Before spending, you can validate the whole tree against Meta with validateOnly: true, see Dry-run a create.

Create a campaign only

POST /v1/ads/campaigns creates a campaign without its first ad set and ad, for when you provision the container first and let ad sets join it later:

const { data } = await zernio.adcampaigns.createAdCampaign({
  body: {
    accountId: 'acc_metaads_123',
    adAccountId: 'act_1234567890',
    name: 'Q4 Push',
    goal: 'conversions',
    budgetAmount: 50,
    budgetType: 'daily',
  },
});
// data.campaign.objective -> the resolved Meta objective
  • goal maps to the ODAX objective exactly like POST /v1/ads/create; the response returns the resolved objective.
  • A budget here is campaign-level (CBO) by definition. Omit it for ABO, where each ad set brings its own budget later.
  • Created PAUSED unless you pass status: "ACTIVE".
  • Ad sets join it via existingCampaignId on the create endpoints, and it shows up in /v1/ads/tree after the next sync pass.

Bid strategy

Meta's full bid-strategy enum is supported on writes (POST /v1/ads/create, POST /v1/ads/boost, PUT /v1/ads/ad-sets/{adSetId}, PUT /v1/ads/campaigns/{campaignId}) and surfaced on reads (GET /v1/ads/tree, /v1/ads/campaigns, /v1/ads/{adId}).

bidStrategyRequired companion fieldNotes
LOWEST_COST_WITHOUT_CAP (default)—Auto-bid; Meta optimizes to spend the budget.
LOWEST_COST_WITH_BID_CAPbidAmount (whole currency units)Auto-bid with a hard ceiling.
COST_CAPbidAmount (whole currency units)Target average cost per result.
LOWEST_COST_WITH_MIN_ROASroasAverageFloor (decimal multiplier, e.g. 2.0 = 2.0× ROAS)Requires a value-optimized campaign (e.g. OUTCOME_SALES with a connected pixel/dataset).

bidAmount is whole currency units of the ad account (USD: 5 = $5.00; JPY: 100 = ¥100). Internally converted to Meta's smallest-denomination integer (cents for USD).

roasAverageFloor is a decimal multiplier; we encode it as Meta's bid_constraints.roas_average_floor × 10000 (so 2.0 → 20000).

Create an ad with a $5 cost cap:

const result = await zernio.adcampaigns.createStandaloneAd({
  body: {
    accountId: 'ACCOUNT_ID',
    adAccountId: 'act_123',
    name: 'Spring sale',
    goal: 'conversions',
    budgetAmount: 50,
    budgetType: 'daily',
    headline: 'Spring sale',
    body: '20% off everything',
    callToAction: 'SHOP_NOW',
    linkUrl: 'https://example.com',
    imageUrl: 'https://cdn.example.com/banner.jpg',
    bidStrategy: 'COST_CAP',
    bidAmount: 5,
    promotedObject: { pixelId: '1729525464415281', customEventType: 'PURCHASE' },
  },
});

To switch a running ad set's bid strategy, use PUT /v1/ads/ad-sets/{adSetId} with bidStrategy (+ roasAverageFloor / bidAmount as required), see Ad Sets.

Campaign-level PUT /v1/ads/campaigns/{campaignId} accepts bidStrategy only — Meta's spec has no bid_amount or bid_constraints at the campaign level (those live on the ad set). Campaign-level bid edits also require the campaign to be CBO (campaign-level budget); ABO campaigns return 409 with a pointer to the ad-set endpoint.

ROAS + revenue-per-event

Every metrics object on /v1/ads/tree, /v1/ads/campaigns, and /v1/ads/{adId} carries three Meta-specific monetary fields alongside the existing actions + conversions counts:

FieldTypeNotes
actionValues{[action_type]: number}Monetary mirror of actions, from Meta's action_values[]. Values in ad-account native currency (see the campaign's currency field). Populated for the same action types Meta reports values on (purchases, AddToCart with value, etc.).
purchaseValuenumberConvenience sum of purchase-type action values, picked from actionValues via the same priority list as conversions (offsite_conversion.fb_pixel_purchase → omni_purchase → purchase). Same unit as spend.
roasnumberDerived purchaseValue / spend. Recomputed from summed numerator + denominator at ad-set and campaign levels (not averaged across children) so the rollup is mathematically correct. Equivalent to Meta's purchase_roas under default attribution.

Example campaign rollup for a purchase campaign:

"metrics": {
  "spend": 493.39,
  "purchaseValue": 2456.78,
  "roas": 4.98,
  "conversions": 42,
  "costPerConversion": 11.75,
  "actions": { "offsite_conversion.fb_pixel_purchase": 42, "add_to_cart": 138, "link_click": 1205 },
  "actionValues": { "offsite_conversion.fb_pixel_purchase": 2456.78, "add_to_cart": 4230.50 }
}

For cost-per-AddToCart, cost-per-Lead, etc., read the relevant actions[key] divided by spend. Pick offsite_conversion.fb_pixel_* keys in actions / actionValues when available to avoid Meta's parallel pixel+omni+canonical reporting (same conversion counted under multiple keys).

Reading the campaign tree

GET /v1/ads/tree returns nested Campaign → Ad Set → Ad with rolled-up metrics (including conversions and raw actions counts). Each campaign node exposes:

FieldTypeNotes
statusactive | paused | pending_review | …Delivery status derived from child ads
reviewStatusin_review | approved | rejected | with_issues | nullPlatform-side review, distinct from status
platformCampaignStatusstringRaw Meta Campaign.effective_status
campaignIssuesInfoobject[] | nullMeta's raw issues_info[] when delivery issues exist
budgetLevelcampaign | adset | nullCanonical CBO/ABO switch
campaignBudget{ amount, type } | nullPopulated only for CBO
adSetBudget (on ad-set nodes){ amount, type } | nullPopulated only for ABO
isBudgetScheduleEnabledbooleanMirrors Meta Campaign.is_budget_schedule_enabled
currencystringISO 4217 code, budgets are in ad-account native currency, NOT normalized

Default ?source=all (matches the Zernio UI) returns both Zernio-created and platform-discovered ads. Pass ?source=zernio to restrict.

Status axes on ads

Each ad node carries three distinct status fields, and conflating them is the classic dashboard bug:

FieldAnswersNotes
statusIs it delivering?Derived from Meta's effective_status, so it inherits ancestor pauses: an ACTIVE ad under a PAUSED campaign reads paused. Ads whose campaign/ad-set schedule has ended read completed.
configuredStatusWhat did you set?The ad's own on/off toggle, unaffected by ancestors.
reviewStatusWhat does Meta's review say?in_review / approved / rejected / with_issues. A rejected ad and a paused-by-you ad are different problems; this is how you tell them apart.

Filter by Facebook Page

A Meta ad account serves ads for every Page in the Business Manager. ?pageId= (on /v1/ads/tree, /v1/ads, and /v1/ads/campaigns) prunes to one Page:

  • On /v1/ads/tree: only ads whose creative is backed by that Page; campaigns and ad sets with no ad on the Page drop out, and rolled-up metrics cover only the Page's ads.
  • On /v1/ads/campaigns: keeps campaigns having at least one ad on the Page, with adCount and metrics computed over those ads only.
  • On /v1/ads: matches each ad's creative.pageId (also returned on reads, so you can group client-side).

Meta only. Rare creatives with no page signal (some IG-only ones) never match.

Update a CBO campaign budget

Use PUT /v1/ads/campaigns/{campaignId} when budgetLevel === 'campaign'. If you call this on an ABO campaign, Zernio returns 409 with code: "BUDGET_LEVEL_MISMATCH", route to the ad-set endpoint instead.

await zernio.adcampaigns.updateAdCampaign({
  path: { campaignId: 'CAMPAIGN_ID' },
  body: {
    platform: 'facebook',
    budget: { amount: 250, type: 'daily' },
  },
});

Campaign spend cap

The same endpoint accepts platformSpecificData.spendCap: a lifetime ceiling across the whole campaign, in whole currency units, independent of the daily/lifetime budget.

// Set a $1,000 lifetime cap
await zernio.adcampaigns.updateAdCampaign({
  path: { campaignId: 'CAMPAIGN_ID' },
  body: { platform: 'facebook', platformSpecificData: { spendCap: 1000 } },
});

// Remove the cap
await zernio.adcampaigns.updateAdCampaign({
  path: { campaignId: 'CAMPAIGN_ID' },
  body: { platform: 'facebook', platformSpecificData: { spendCap: null } },
});

Removal is explicit null, not 0. Meta rejects a spend cap of zero (subcode 1885099, "Spend Cap Can't Be Zero"); its removal sentinel is a magic number Zernio sends for you when it receives spendCap: null. Unknown keys inside platformSpecificData return a 400, and so does a non-Meta platform.

This is the campaign cap. The account-level spend cap is read via Account finances.

Everything else

  • Ad-set budgets, schedule, optimization goal, promotedObject and the learning phase: Ad Sets
  • Pause / resume (single and bulk), duplicate at all three levels, delete, and dry-run: Duplicate & Lifecycle
  • Raw insights beyond the rolled-up metrics: Insights
Was this page helpful?

Meta Ads

Create, boost, measure and manage Facebook + Instagram ads with Zernio API

Ad Sets

Ad set budgets, schedule, optimization goal, and post-launch delivery edits

On this page

Create the full tree in one callCreate a campaign onlyBid strategyROAS + revenue-per-eventReading the campaign treeStatus axes on adsFilter by Facebook PageUpdate a CBO campaign budgetCampaign spend capEverything else