Update a campaign
Changes a campaign's settings, such as its name, budget, bidding or tracking URLs.
Send at least one of budget, bidStrategy,
portfolioBidStrategyId, targetImpressionShare, manualCpc, networkSettings,
trackingUrlTemplate, finalUrlSuffix, sharedBudgetId, name or platformSpecificData. An unsupported
field is always an error, never a silent drop.
| Body field | Meta | Others | |
|---|---|---|---|
bidStrategy | Yes | Yes | 501 |
bidAmount, roasAverageFloor | 400 (ad-set level) | Yes | 400 |
portfolioBidStrategyId | 400 | Yes | 400 |
targetImpressionShare | 400 | Search only | 400 |
manualCpc | 400 | Search and Display | 400 |
networkSettings | 400 | Search only | 400 |
trackingUrlTemplate, finalUrlSuffix | 400 | Yes | 400 |
sharedBudgetId | 400 | Yes | 400 |
budget (CBO; ABO returns 409) | Yes | Daily only | OpenAI and Reddit: daily or lifetime; others 501 |
name | Yes | 501 | Reddit: Yes; others 501 |
platformSpecificData.spendCap | Yes | 400 | 400 |
accountId (empty campaigns) | Yes | - | - |
Meta budget edits check the live campaign budget, so an older local ABO stamp cannot block a CBO campaign. A successful edit repairs local ad budget fields. A live ABO campaign still returns 409 with the ad-set budget endpoint.
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
(exclusive with bidStrategy). targetImpressionShare switches the campaign to
Target impression share and manualCpc: { maxCpc } to Manual CPC (every ad group of the campaign gets maxCpc as its bid, in the same mutate); each is exclusive with
every other strategy field. networkSettings, trackingUrlTemplate and
finalUrlSuffix go out in one campaign update, each field sent written on its own
leaf, so an omitted one keeps its current value; an empty string clears a URL field.
Setting the standard triplet on a campaign that
is currently on a PORTFOLIO strategy is rejected: detach it in Google Ads
first, since it is shared across campaigns.
Google budget updates read the current budget before mutation. Shared budgets return 409 unless allowSharedBudgetUpdate=true is explicitly supplied, because the change affects every campaign using that budget. A budget counts as shared when Google flags it as shared or when more than one campaign uses it (older budgets can serve several campaigns without the flag). Unknown sharing state also returns 409.
sharedBudgetId (Google) moves the campaign onto a shared budget from
GET /v1/ads/shared-budgets, which also needs allowSharedBudgetUpdate: true (409
otherwise) because the campaign then splits that budget with every campaign on it;
budget cannot ride along. sharedBudgetId: null moves it back onto a new budget of its
own, sized by budget (required, daily); the new budget and the switch go out in one
atomic Google mutate. A campaign that already has its own budget returns 409 for null.
The budget a campaign leaves is not removed. The response carries the budget the
campaign now uses.
OpenAI Ads campaigns carry exactly one spend cap: budget.type daily or lifetime
replaces whichever cap the campaign had, with a minimum of 1 in the ad account's
currency (422 below it). Lifetime can switch to daily, but OpenAI never switches a
daily cap back to lifetime (422).
Reddit Ads: budget changes the amount of a campaign budget (Reddit campaign budget
optimization); a campaign whose ad groups carry the budget returns 409 or 422, use
PUT /v1/ads/ad-sets/{adSetId} there. Reddit cannot switch a published budget between
daily and lifetime, so budget.type must match the current one (422 otherwise).
bidStrategy is set per ad group on Reddit (PUT /v1/ads/ad-sets/{adSetId}).
accountId forwards the update straight to Meta for a campaign with zero ads,
which would otherwise 404; the response then carries updated: 0.
Authorization
bearerAuth API key authentication: send your Zernio API key in the Authorization header, prefixed with Bearer.
In: header
Path Parameters
Platform campaign ID
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
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.updateAdCampaign({ path: { campaignId: 'campaign_abc123', }, body: { platform: 'facebook', },});console.log(data);{ "updated": 0, "budget": { "amount": 0, "type": "daily", "amountMicros": "string", "explicitlyShared": true, "resourceName": "string", "deliveryMethod": "string" }, "budgetLevel": "campaign", "bidStrategy": "LOWEST_COST_WITHOUT_CAP", "bidAmount": 0, "roasAverageFloor": 0, "portfolioBidStrategyId": "string", "targetImpressionShare": { "location": "ANYWHERE_ON_PAGE", "percent": 1, "maxCpc": 1 }, "manualCpc": { "maxCpc": 1 }, "networkSettings": { "searchPartners": true, "displayNetwork": true }, "trackingUrlTemplate": "string", "finalUrlSuffix": "string", "sharedBudgetId": "string", "platformSpecificData": {}}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.
Duplicate a campaign
Copies a campaign with its ad sets, ads, creatives and targeting, created paused so you can review it before launch. Duplicates a campaign, including its ad sets, ads, creatives, and targeting by default (`deepCopy: true`). The copy is created paused so callers can review before launching. Per-platform implementation: - **Meta** uses the native `POST /{campaign-id}/copies` endpoint. - **TikTok** has no native copy primitive; Zernio walks the source graph (`/v2/campaign/get/`, `/v2/adgroup/get/`, `/v2/ad/get/`) and recreates each entity via the corresponding `/create/` endpoints, carrying over budget / targeting / bid_type / bid_price / deep_bid_type / creative fields. Spark Ad linkage (`tiktok_item_id`) is preserved. - **LinkedIn** has no native copy primitive; Zernio walks the source CampaignGroup → Campaigns → Creatives and recreates each entity, carrying over `type` / `costType` / `unitCost` / `optimizationTargetType` / `creativeSelection` / `objectiveType` / `format` / `dailyBudget` / `totalBudget` / `targetingCriteria` / `runSchedule` and every Creative's `content` object verbatim. `statusOption: INHERITED_FROM_SOURCE` is evaluated **per entity**: any Group / Campaign / Creative whose source is `ACTIVE` gets its clone activated too. Duplicating an ACTIVE campaign with `INHERITED_FROM_SOURCE` starts a second front of spend the moment the clone activates. The safe default is `PAUSED`. The new hierarchy is asynchronous to materialize in our DB, and we trigger sync discovery automatically. Set `syncAfter: false` to skip and poll `/v1/ads/tree` on your own cadence. Other platforms return 501 Not Implemented.