Update a campaign
Campaign-level edits. Send at least one of budget, bidStrategy,
portfolioBidStrategyId, 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 |
budget (CBO; ABO returns 409) | Yes | 501 | 501 |
name | Yes | 501 | 501 |
platformSpecificData.spendCap | Yes | 400 | 400 |
accountId (empty campaigns) | Yes | - | - |
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). 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.
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 - use your Zernio API key as a Bearer token
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
{ "updated": 0, "budget": { "amount": 0, "type": "daily" }, "budgetLevel": "campaign", "bidStrategy": "LOWEST_COST_WITHOUT_CAP", "bidAmount": 0, "roasAverageFloor": 0, "portfolioBidStrategyId": "string", "platformSpecificData": {}}Create a standalone campaign
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 only `budgetType: lifetime`, Google only `budgetType: daily`). 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`. TikTok campaigns are created without a status and report `ENABLE`. Created `PAUSED` unless `status: ACTIVE` where the platform supports it. **Idempotency:** send an `Idempotency-Key` header to make retries safe.
Duplicate a campaign
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 — 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.