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 CommentsEnums, Limits & Error Codes
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Meta Ads

Ad Sets

Edit a Meta ad set after launch: ABO budget, status, optimization goal, schedule, promoted object, read its learning phase live, and delete it.


The ad set decides delivery: who sees the ad, when, how Meta optimizes and, on ABO campaigns, how much is spent. POST /v1/ads/create builds one for you (campaigns); the calls on this page edit it afterwards without recreating the ad, or delete it. You need the adSetId, which is platformAdSetId on the create response and on every ad set node of GET /v1/ads/tree.

SettingOwned byEdit with
Budget (ABO)Ad setPUT /v1/ads/ad-sets/{adSetId}
StatusAd setThe same endpoint, or /status
Bid strategy, bidAmount, roasAverageFloorAd setThe same endpoint (bid strategy)
TargetingAd setPUT /v1/ads/{adId} with targeting; on Meta the edit lands on the ad's ad set
Optimization goal, billing event, schedule, promotedObjectAd setplatformSpecificData
Budget (CBO)CampaignPUT /v1/ads/campaigns/{campaignId}
Deletion (cascades to the ads)Ad setDELETE /v1/ads/ad-sets/{adSetId}

campaignAttribution (AEM or SKADNETWORK, for iOS 14+ app promotion) is create-only: it has no standalone ad-set creation endpoint and no edit path here, only POST /v1/ads/create (SKAdNetwork attribution).

Update budget and status (ABO)

Call PUT /v1/ads/ad-sets/{adSetId} with platform and at least one of budget, status, name or bidStrategy, when the campaign node reads budgetLevel: "adset".

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: adSet } = await zernio.adcampaigns.updateAdSet({
  path: { adSetId: '120250000000000001' },
  body: {
    platform: 'facebook',
    budget: { amount: 75, type: 'daily' },
    status: 'active'
  }
});

Response (200):

{
  "budget": { "amount": 75, "type": "daily" },
  "budgetLevel": "adset",
  "status": "active",
  "statusUpdated": 1,
  "statusSkipped": 0
}

Budget amounts are whole currency units of the ad account (USD 75 is $75.00). Read currency off the campaign node of the tree before you show or send a number; budgets are never normalized to one currency. statusUpdated: 0 on a resume is normal when the ads are all waiting on Meta review.

When statusSkipped is above 0, statusSkippedReasons appears and says why in one sentence per group: 2 ads in terminal status, 1 ad already active, or 3 ads switched on but not yet delivering; the platform sets their status. It is left out of the response when nothing was skipped.

Status-only toggles

For pause and resume without touching the budget, PUT /v1/ads/ad-sets/{adSetId}/status takes { status, platform } and mirrors PUT /v1/ads/campaigns/{campaignId}/status. The lifecycle page covers the campaign-level, bulk and single-ad variants.

Post-launch delivery edits

The Meta-only delivery settings go in platformSpecificData on the same PUT /v1/ads/ad-sets/{adSetId}:

const { data: delivery } = await zernio.adcampaigns.updateAdSet({
  path: { adSetId: '120250000000000001' },
  body: {
    platform: 'facebook',
    platformSpecificData: {
      optimizationGoal: 'OFFSITE_CONVERSIONS',
      billingEvent: 'IMPRESSIONS',
      startDate: '2027-03-01T09:00:00Z',
      endDate: '2027-03-31T23:59:00Z',
      promotedObject: { pixelId: '1729525464415281', customEventType: 'PURCHASE' }
    }
  }
});

Response (200):

{
  "budgetLevel": "adset",
  "platformSpecificData": {
    "optimizationGoal": "OFFSITE_CONVERSIONS",
    "billingEvent": "IMPRESSIONS",
    "startDate": "2027-03-01T09:00:00Z",
    "endDate": "2027-03-31T23:59:00Z",
    "promotedObject": { "pixelId": "1729525464415281", "customEventType": "PURCHASE" }
  }
}
FieldMeaning
optimizationGoalForwarded verbatim. Meta validates it against the campaign objective and rejects incompatible pairs.
billingEventWhat Meta charges for (IMPRESSIONS, LINK_CLICKS, THRUPLAY). On create it is a top-level field of POST /v1/ads/create, like optimizationGoal, promotedObject and startDate there.
startDate, endDateISO 8601 strings. Setting endDate schedules the stop. An existing end cannot be cleared here: null fails validation with a 400, and only a new date-time replaces the old one.
promotedObjectThe full object, replaced wholesale. Accepted keys are on conversion campaigns.
dailyMinSpendTarget, lifetimeMinSpendTargetMeta's minimum spend for this ad set, in whole currency units. Reserves a share of a campaign (CBO) budget, so the campaign must own the budget.

platformSpecificData and promotedObject are strict. An unknown or misspelled key returns a 400 naming the path (platformSpecificData.optimisationGoal) instead of being dropped, and a non-Meta platform returns 400 too. A dropped promotedObject key used to change what the ad set optimized toward without warning.

Meta validates combinations on its side and its message surfaces verbatim: switching an OUTCOME_TRAFFIC ad set to OFFSITE_CONVERSIONS without a pixel on promotedObject returns Meta's own error, not a generic failure.

Read an ad set live

Call GET /v1/ads/ad-sets/{adSetId} with accountId to read the ad set from Meta instead of from Zernio's synced copy, when you need the current state right now, learning phase included.

const { data: live } = await zernio.adcampaigns.getAdSetDetails({
  path: { adSetId: '120250000000000001' },
  query: { accountId: '66b2e19d8c3f5a7e9d0b1c2d' }
});

console.log(live.adSet.learning_stage_info?.status);

Response (200), Meta's ad set node as returned:

{
  "adSet": {
    "id": "120250000000000001",
    "name": "Spring sale - US feed - Ad Set",
    "status": "ACTIVE",
    "effective_status": "ACTIVE",
    "optimization_goal": "LINK_CLICKS",
    "billing_event": "IMPRESSIONS",
    "daily_budget": "7500",
    "learning_stage_info": {
      "status": "LEARNING",
      "conversions": 12,
      "last_sig_edit_ts": 1788606000
    }
  }
}

The default projection includes learning_stage_info; pass fields to replace it with your own Meta field list. Values here are Meta's raw shapes: daily_budget is in minor units, unlike every Zernio budget field.

Learning phase

learning_stage_info.status is Meta's own verdict on whether the ad set has exited the learning phase:

statusMeaning
LEARNINGStill learning. Delivery and cost are unstable, and a significant edit restarts the clock.
SUCCESSExited learning.
FAILLearning limited: the ad set never gathered about 50 optimization events in a week. Broaden targeting, raise the budget or consolidate ad sets.
WAIVINGMeta waived the requirement for this ad set.

last_sig_edit_ts is the last significant edit in Unix seconds and can be absent on a SUCCESS entry. Meta omits learning_stage_info.status on paused ad sets and on many older active ones (they return only attribution_windows): treat a missing status as unknown, not as exited. Zernio also records the learning stage during the regular ads sync, so ad documents from GET /v1/ads and GET /v1/ads/tree carry it without a live call; this endpoint is for when you need it fresher than the last sync pass.

Add an ad to an existing ad set

Pass adSetId on POST /v1/ads/create to attach one more ad to an ad set Meta has already optimized on; it inherits budget, targeting, schedule and goal. See attach a creative to an existing ad set.

Duplicate an ad set

POST /v1/ads/ad-sets/{adSetId}/duplicate copies the ad set, by default with its ads and creatives, optionally into another campaign. See duplicate an ad set or an ad.

Delete an ad set

DELETE /v1/ads/ad-sets/{adSetId} removes the ad set on the platform and cascades to its ads; the campaign above it is never touched. Every ad document under it is marked status: "cancelled" locally, and adCount says how many.

curl -X DELETE "https://zernio.com/api/v1/ads/ad-sets/120250000000000001" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

Response (200):

{ "deleted": true, "adCount": 3 }

The delete is hard on Meta and on Google (which removes the ad group), and soft where the platform has no hard delete: LinkedIn moves the campaign to PENDING_DELETION, Pinterest archives the ad group, X soft-flags the line item. All of them stay readable for reporting. To stop spend without losing the ad set, pause it instead.

If it fails

A 400 with param set means a key inside platformSpecificData or promotedObject is not one Zernio accepts:

{
  "error": "Unknown key platformSpecificData.optimisationGoal",
  "type": "invalid_request_error",
  "param": "platformSpecificData.optimisationGoal"
}

Fix the spelling against the table above. A 409 with code: "BUDGET_LEVEL_MISMATCH" means the campaign is CBO: send the budget to PUT /v1/ads/campaigns/{campaignId} instead.

Related

  • Campaigns: the create call, bid strategy and the tree.
  • Conversion campaigns: every promotedObject key.
  • Lifecycle: pause, resume and duplicate.
  • Update ad set and Live ad set details: every field.
Was this page helpful?

Campaigns

Create a Meta campaign, ad set and ad in one call, set the bid strategy and its value rule sets, and read the campaign tree back with rolled-up metrics.

Creatives

Build Meta ad creatives on POST /v1/ads/create: attach to a running ad set, video, one asset per placement, carousels, multi-language ads, Advantage+ enhancements, swap the creative on a live ad, and read the media it serves.

On this page

Update budget and status (ABO)Status-only togglesPost-launch delivery editsRead an ad set liveLearning phaseAdd an ad to an existing ad setDuplicate an ad setDelete an ad setIf it failsRelated