---
title: "Duplicate a campaign"
method: POST
path: "/v1/ads/campaigns/{campaignId}/duplicate"
tags: ["Ad Campaigns"]
---

# Duplicate a campaign

`POST /v1/ads/campaigns/{campaignId}/duplicate`

## Request

- Base URL: `https://zernio.com/api`
- URL: `https://zernio.com/api/v1/ads/campaigns/{campaignId}/duplicate`
- Auth: HTTP bearer

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.

## Path parameters

- `campaignId` string, required

## Headers

- `Idempotency-Key` string

## Request body

- object
  - `platform` 'facebook' | 'instagram' | 'tiktok' | 'linkedin', required
  - `deepCopy` boolean: Copy child ad sets + ads + creatives + targeting
  - `statusOption` 'ACTIVE' | 'PAUSED' | 'INHERITED_FROM_SOURCE': ACTIVE = launch the clone immediately (spends the moment LinkedIn approves it). PAUSED = clone stays DRAFT, safe default. INHERITED_FROM_SOURCE = mirror each entity's source status per-entity. Duplicating an ACTIVE campaign this way starts a second front of spend.
  - `startTime` string, date-time: Reschedule the copied hierarchy's start (ISO 8601). On Meta and TikTok a value without an offset (`YYYY-MM-DD`, `YYYY-MM-DD HH:MM:SS` or `YYYY-MM-DDTHH:MM:SS`) is read in the ad account timezone; LinkedIn ad accounts carry no timezone, so there it is read as UTC. TikTok defaults to a start a few minutes after the copy.
  - `endTime` string, date-time: Reschedule the copied hierarchy's end, read like `startTime`; a date-only end runs to 23:59:59 local. Defaults to the source's end.
  - `renameStrategy` 'DEEP_RENAME' | 'ONLY_TOP_LEVEL_RENAME' | 'NO_RENAME': Meta's native `rename_strategy` values. `DEEP_RENAME` renames the copied campaign and every copied child (ad sets, ads) with `renamePrefix` / `renameSuffix`. `ONLY_TOP_LEVEL_RENAME` renames only the copied campaign; children keep their source names. `NO_RENAME` keeps every source name. With no rename option at all, Meta appends its own ` - Copy` suffix; LinkedIn defaults to `DEEP_RENAME` (its campaign group and campaigns are renamed). Ignored on TikTok, where `renamePrefix` / `renameSuffix` apply to every copied object.
  - `renamePrefix` string: Text prepended to each renamed object's name.
  - `renameSuffix` string: Text appended to each renamed object's name. On LinkedIn an omitted suffix defaults to ` (Copy)`.
  - `syncAfter` boolean: Trigger ads discovery on the owning account after the copy succeeds

## Response `200`

Campaign duplicated

- object
  - `copiedCampaignId` string: Platform ID of the new campaign
  - `discovery` 'triggered' | 'skipped' | 'failed'
  - `raw` object: Platform-native response from the copy endpoint (Meta includes ad_object_ids for child copies)

## Other responses

- `400`: Invalid input
- `401`: Missing or invalid API key. `code` is `missing_credentials` when no Authorization header was sent and `invalid_credentials` when the key is unknown, revoked or expired.
- `403`: Returned with code `ads_allowance_exceeded` when the team has no payment method on file and has reached the 500 free live ads: add a card to resume. Also returned with code `ad_account_not_selected` when the connection has selected ad accounts and the ad account this request targets is not among them; `details` carries `accountId`, `adAccountId` and `selectedAdAccountIds`. Add the ad account to the selection (GET /v1/connect/{platform}/ads with adAccountIds, which replaces the selection) or use a connection that has it selected.
- `404`: Source campaign not found
- `501`: Operation not supported on this platform

## Changes

- **2026-10-06** `f8c8f34fb41a`: 1 warning
  - added the new `account` enum value to the `details/budgetScope` response property for the response status `401`
- **2026-10-02** `d556591af3f6`: 7 info
  - added the optional property `code` to the response with the `401` status
  - added the optional property `details` to the response with the `401` status
  - added the optional property `docUrl` to the response with the `401` status
  - added the optional property `param` to the response with the `401` status
  - …3 more
- **2026-09-02** `017dff7e4324`: 1 info
  - added the non-success response with the status `403`

[Operation history](https://skmtc.dev/zernio/apis/zernio-api/changes/v1/ads/campaigns/:campaignId/duplicate/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/4e74db179e73?raw)
