---
title: "Create a standalone campaign"
method: POST
path: "/v1/ads/campaigns"
tags: ["Ad Campaigns"]
---

# Create a standalone campaign

`POST /v1/ads/campaigns`

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.

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `accountId` string, required — Zernio SocialAccount id (posting or ads variant); its platform decides where the campaign is created.
  - `adAccountId` string, required — Platform ad account id (Meta act_<n>, Google customer id, LinkedIn account id, ...).
  - `name` string, required
  - `goal` 'engagement' | 'traffic' | 'awareness' | 'video_views' | 'lead_generation' | 'lead_conversion' | 'job_applicants' | 'conversions' | 'app_promotion' | 'catalog_sales' | 'page_likes', required — Mapped to the ODAX objective (same mapping as POST /v1/ads/create).
  - `isSkadnetworkAttribution` boolean — Meta app promotion only. Immutable campaign flag. Set true for iOS 14+ SKAdNetwork campaigns and supply promotedObject.applicationId plus promotedObject.objectStoreUrl. The campaign receives promotedObject only when this flag is true. Cannot be changed on an existing campaign.
  - `promotedObject` AdPromotedObject — What the ad optimises against. Behaviour depends on the platform. **Meta**: forwarded to the ad set's `promoted_object` (snake-cased). For `goal: app_promotion`, it is also sent on the campaign only when `isSkadnetworkAttribution: true`. Plain Android app installs keep the existing campaign payload, with the promoted object only on the ad set. POST /v1/ads/campaigns forwards this object only for that explicit SKAN flag. Required for goals whose ad-set optimization_goal points at a specific event/page/app (without it Meta rejects the ad-set create with `error_subcode: 1815430` "Please select a promoted object for your ad set"): - `goal: conversions` / `lead_conversion` (OFFSITE_CONVERSIONS): requires `pixelId` + `customEventType`, or `customConversionId` when optimising against a Custom Conversion (the conversion carries its own event definition). For a pixel CUSTOM event (one you named yourself in CAPI/Events Manager), send `customEventType: OTHER` + `customEventStr` with the event name. - `goal: app_promotion` (APP_INSTALLS): requires `applicationId` + `objectStoreUrl` - `goal: lead_generation` (LEAD_GENERATION): `pageId` is auto-filled from the connected Page when omitted Other Meta goals (engagement, traffic, awareness, video_views) ignore this field. **TikTok**: used by `goal: conversions` and the Smart+ goals (`smartPlus: true`). - `pixelId` maps to the ad group's `pixel_id`. Required: a TikTok website-conversion ad group without a pixel is rejected with `40002: Please select a pixel`. - `customEventType` maps to the ad group's `optimization_event` (the pixel event to optimise for). Optional on the regular conversions flow, required on Smart+. See the `customEventType` field below for the valid TikTok codes. - `applicationId` (Smart+ `goal: app_promotion` only) maps to the ad group's `app_id`: the App ID of an app registered on the TikTok Ads account (Assets → Events → App Events). Install optimization needs the app's MMP tracking configured. The remaining `promotedObject.*` fields are Meta-only. Platforms other than Meta and TikTok ignore `promotedObject` entirely.
    - `pixelId` string — Pixel ID. **Meta:** Facebook Pixel ID, required for `goal: conversions`. Requires `customEventType` alongside it; Meta rejects any promoted_object carrying `pixel_id` without `custom_event_type` (error_subcode 1885014), even when `customConversionId` is also present. **TikTok:** TikTok Pixel ID, required for `goal: conversions`. To discover the pixels an ad account can use, call `GET /v1/accounts/{accountId}/tracking-tags?adAccountId=act_...` (each entry carries `kind` and `ownerAdAccountId`), or `GET /v1/accounts/{accountId}/conversion-destinations`. Note this is a different resource from `GET /v1/ads/{adId}/tracking-tags`, which reads an ad's click-URL params (`url_tags`), not pixels.
    - `customEventType` string — The event the campaign/ad group optimises against. **Meta:** standard event like `PURCHASE`, `LEAD`, `COMPLETE_REGISTRATION`, `ADD_TO_CART`. Uppercased internally so callers can pass any case. Required for `goal: conversions`. **TikTok:** an `optimization_event` code (UPPER_SNAKE, not Meta's vocabulary and not PascalCase), OR the exact event name shown in TikTok Events Manager (auto-resolved to its code). Must be one of the event types your TikTok Pixel tracks; custom events are not optimizable. Current taxonomy: `SHOPPING` (Purchase), `ON_WEB_CART` (Add to Cart), `INITIATE_ORDER` (Initiate Checkout), `FORM` (Lead), `ON_WEB_REGISTER` (Complete Registration), `ON_WEB_DETAIL` (View Content). `ON_WEB_ORDER` is deprecated. On rejection the error lists the event types your pixel actually tracks. Optional for `goal: conversions`.
    - `customEventStr` string — Meta only. Pixel custom-event name to optimise against (Meta's `custom_event_str`), exactly as it appears in Events Manager and in your CAPI payloads (case-sensitive, not uppercased). Requires `customEventType: OTHER`, and `OTHER` requires this field (400 either way). The same as picking a custom event in Ads Manager's conversion-event dropdown. For rule-based Custom Conversions use `customConversionId` instead.
    - `pageId` string — Facebook Page ID. Used by `goal: lead_generation`. Auto-filled from the connected Page when omitted.
    - `applicationId` string — App ID. Required for `goal: app_promotion`.
    - `objectStoreUrl` string, uri — App Store / Play Store listing URL. Required for `goal: app_promotion`.
    - `customConversionId` string — Custom Conversion ID, when optimising against one instead of a standard event. Accepted alone by this API, without `pixelId` or `customEventType`. If `pixelId` is also sent, `customEventType` is still required on the promoted_object (Meta rejects `pixel_id` without `custom_event_type`, error_subcode 1885014).
    - `productCatalogId` string — Optional catalog ID. If supplied with productSetId, the set must belong to this catalog. A catalog ID cannot replace productSetId.
    - `productSetId` string — Meta product SET ID from GET /v1/ads/catalogs/{catalogId}/product-sets. Zernio checks that the token can read the set and its product_catalog before creation. A catalog ID or inaccessible set returns a precise 400 naming promotedObject.productSetId. A mismatch with productCatalogId names promotedObject.productCatalogId.
    - `offlineConversionDataSetId` string — Meta only. Offline event set (dataset) to optimise toward. Post-merger these are datasets: the id is the dataset id (for pixel-backed datasets, the pixel id).
    - `whatsappPhoneNumber` string — Meta only. WhatsApp number on messaging-destination ad sets.
  - `buyingType` 'AUCTION' | 'RESERVED' — Every platform buys at auction by default, so this only needs sending on Meta, and only to choose RESERVED. `AUCTION` is accepted on every platform and changes nothing. `RESERVED` (Reach & Frequency) is Meta-only and is rejected with a 400 elsewhere. SKAdNetwork app promotion requires AUCTION.
  - `validateOnly` boolean — Meta only. Runs campaign validation without creating or persisting a campaign; Idempotency-Key storage is bypassed. Returns HTTP 200 with validateOnly true and status VALIDATED.
  - `specialAdCategories` string[]
  - `budgetAmount` number — Campaign-level (CBO) budget in WHOLE currency units (USD: 50 = $50.00), NOT cents. Meta's own Marketing API takes this same number in minor units, so it is an easy and expensive mix-up. Requires budgetType.
  - `budgetType` 'daily' | 'lifetime'
  - `status` 'ACTIVE' | 'PAUSED'
  - `bidStrategy` 'LOWEST_COST_WITHOUT_CAP' | 'LOWEST_COST_WITH_BID_CAP' | 'COST_CAP' | 'LOWEST_COST_WITH_MIN_ROAS' — Campaign bid strategy. Meta stores `bid_strategy` alongside the budget, so this REQUIRES `budgetAmount` + `budgetType` on the same request; sending it without a campaign budget is a 400. A campaign carrying a strategy without its `bid_amount` makes every ad set created under it fail with an error that names the ad set (code 100, subcode 1815857), so the bad state is rejected up front rather than accepted. To bid at ad-set level on Meta, set the strategy there instead. 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.
  - `bidAmount` number — Whole currency units (USD: 5 = $5.00). Required for LOWEST_COST_WITH_BID_CAP and COST_CAP; ignored otherwise. On Meta, validated here but NOT stored: the campaign object has no bid_amount field, only bid_strategy lives on it, and the amount takes effect once an ad set joins this campaign (existingCampaignId on POST /v1/ads/create) and supplies its own bidAmount there. On Google, stored directly on the campaign's bidding strategy.
  - `roasAverageFloor` number — Decimal ROAS multiplier (2.0 = 2.0x). Required for LOWEST_COST_WITH_MIN_ROAS.
  - `portfolioBidStrategyId` string — Google only. Attach an existing portfolio bid strategy (numeric id from GET /v1/ads/bid-strategies) to the new campaign instead of a standard one. Exclusive with bidStrategy.

## Response `200`

Campaign validation passed without creating a campaign.

- object
  - `validateOnly` boolean — Always true.
  - `adAccountId` string
  - `campaignId` '' — Empty because no campaign was created.
  - `objective` string
  - `status` 'VALIDATED'

## Other responses

- `201` — Campaign created
- `400` — Invalid input, or Meta rejected the create
- `401` — Unauthorized
- `404` — The account or requested resource was not found or is not accessible. An account ID may have been disconnected and removed. Read GET /v1/accounts for current account IDs.
- `409` — The account exists but is inactive or needs reconnection. Reconnect it, then read GET /v1/accounts for its current account ID before retrying. Code: ads_connection_required.
- `501` — Only supported on Meta (facebook/instagram)

## Changes

- **2026-09-16** `3e6ddf2a99ea` — 2 info
  - added the optional property `details/budgetScope` to the response with the `404` status
  - added the optional property `details/budgetScope` to the response with the `409` status
- **2026-09-15** `0dba7d004d75` — 5 info
  - the `buyingType` request property default value `AUCTION` was removed
  - added the optional property `details/quotaExhausted` to the response with the `404` status
  - added the optional property `details/quotaExhausted` to the response with the `409` status
  - added the optional property `details/quotaScope` to the response with the `404` status
  - …1 more
- **2026-09-10** `e70ed06e7150` — 3 info
  - the `buyingType` request property default value `AUCTION` was added
  - added the non-success response with the status `404`
  - added the non-success response with the status `409`
- **2026-09-09** `222e3a38a382` — 5 info
  - added the new optional request property `buyingType`
  - added the new optional request property `isSkadnetworkAttribution`
  - added the new optional request property `promotedObject`
  - added the new optional request property `validateOnly`
  - …1 more
- **2026-09-08** `d7a8fa118ef0` — 1 info
  - added the new optional request property `portfolioBidStrategyId`

[Full history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/ads/campaigns/post.md)

---

[API](https://skmtc.dev/zernio/apis/zernio-api.md) · [All operations](https://skmtc.dev/zernio/apis/zernio-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zernio/apis/zernio-api/revisions/dd3865482f9f?raw)
