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 —headlineis ignored,bodybecomesad_text). - Google: status, budget, KEYWORD edits via
targeting.keywords/targeting.negativeKeywords, and DEVICE bid adjustments viatargeting.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 othertargetingfield returns 400: Google cannot mutate broad targeting post-create without recreating the campaign.creativereturns 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
targetingorcreativereturns 501 with codeunsupported_platform_operation. OpenAI Ads budget is lifetime-only (seebudget.typebelow).
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.
Use PATCH /v1/ads/keywords/{keywordId} to pause/enable one keyword, or
DELETE /v1/ads/keywords/{keywordId} to remove it.
Authorization
bearerAuth API key authentication - use your Zernio API key as a Bearer token
In: header
Path Parameters
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
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"}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).
Duplicate an ad
Duplicates a single ad via Meta's native `POST /{ad-id}/copies`. The copy is created paused. `adSetId` retargets the copy into another ad set; omitted = the source's own ad set. Accepts the Zernio ad id or the platform ad id. Sync discovery is triggered automatically (`syncAfter: false` to skip).