import { WhopClient } from "@whop/sdk";
const client = new WhopClient({ token: "YOUR_TOKEN", apiVersionDate: "2026-08-21-1", idempotencyKey: "YOUR_IDEMPOTENCY_KEY" });
await client.ads.duplicate({
id: "id"
});curl --request POST \
--url https://api.whop.com/api/v1/ads/{id}/duplicate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"count": 2,
"preserve_engagement": true,
"target_ad_group_id": "adgrp_xxxxxxxxxxxxxx"
}
'import requests
url = "https://api.whop.com/api/v1/ads/{id}/duplicate"
payload = {
"count": 2,
"preserve_engagement": True,
"target_ad_group_id": "adgrp_xxxxxxxxxxxxxx"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
count: 2,
preserve_engagement: true,
target_ad_group_id: 'adgrp_xxxxxxxxxxxxxx'
})
};
fetch('https://api.whop.com/api/v1/ads/{id}/duplicate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.whop.com/api/v1/ads/{id}/duplicate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'count' => 2,
'preserve_engagement' => true,
'target_ad_group_id' => 'adgrp_xxxxxxxxxxxxxx'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.whop.com/api/v1/ads/{id}/duplicate"
payload := strings.NewReader("{\n \"count\": 2,\n \"preserve_engagement\": true,\n \"target_ad_group_id\": \"adgrp_xxxxxxxxxxxxxx\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.whop.com/api/v1/ads/{id}/duplicate")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"count\": 2,\n \"preserve_engagement\": true,\n \"target_ad_group_id\": \"adgrp_xxxxxxxxxxxxxx\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.whop.com/api/v1/ads/{id}/duplicate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"count\": 2,\n \"preserve_engagement\": true,\n \"target_ad_group_id\": \"adgrp_xxxxxxxxxxxxxx\"\n}"
response = http.request(request)
puts response.read_body{
"data": [
{
"ad_campaign": {
"id": "adcamp_xxxxxxxxxxxxxx"
},
"ad_group": {
"id": "adcamp_xxxxxxxxxxxxxx"
},
"added_to_cart_value": 1850,
"added_to_carts": 74,
"call_to_action": "learn_more",
"click_through_rate": 0.0261,
"clicks": 1337,
"completed_registration_value": 95,
"completed_registrations": 19,
"contact_value": 175,
"contacts": 27,
"cost_per_added_to_cart": 6.515675675675676,
"cost_per_click": 0.36,
"cost_per_completed_registration": 25.37684210526316,
"cost_per_contact": 17.85777777777778,
"cost_per_lead": 3.984793388429752,
"cost_per_mille": 9.42,
"cost_per_purchase": 12.68842105263158,
"cost_per_result": 7.653333333333333,
"cost_per_schedule": 10.48173913043478,
"cost_per_submitted_application": 40.18,
"cost_per_unique_click": 0.48,
"cost_per_viewed_content": 0.2253084112149533,
"created_at": "2026-01-01T12:00:00.000Z",
"creatives": [
{
"crop": {
"height": 1000,
"width": 1000,
"x": 40,
"y": 0
},
"format": "vertical",
"id": "file_xxxxxxxxxxxxxx",
"language": null,
"media_type": "image",
"url": "https://whop-assets-example.s3.amazonaws.com/uploads/image/2026-01-01/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
],
"custom_conversions": 63,
"custom_event_counts": {
"booked_detail": 63
},
"custom_event_values": {
"booked_detail": 4820
},
"delivery_status": "paused",
"descriptions": [
{
"language": "en",
"text": "Austin's mobile detailers"
}
],
"existing_post_id": "xxxxxxxxxxxxxxxx_98765",
"external_accounts": [
{
"id": "adcamp_xxxxxxxxxxxxxx"
}
],
"frequency": null,
"headlines": [
{
"language": "en",
"text": "Austin's mobile detailers"
}
],
"id": "ad_xxxxxxxxxxxxxx",
"impressions": 51204,
"issues": [
{
"category": "error",
"id": "adiss_xxxxxxxxxxxxxx",
"message": "Your ad was rejected for unacceptable business practices. Edit the ad's content and resubmit.",
"resource_id": "ad_xxxxxxxxxxxxxx",
"resource_type": "ad",
"title": "Ad issue"
}
],
"lead_value": 1180,
"leads": 121,
"link_clicks": 1071,
"platform": "meta",
"post_id": "xxxxxxxxxxxxxxxx_98765",
"post_source": "facebook",
"post_thumbnail_url": "https://whop-assets-example.s3.amazonaws.com/uploads/image/2026-01-01/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"primary_texts": [
{
"language": "en",
"text": "Austin's mobile detailers"
}
],
"purchase_value": 9462,
"purchases": 38,
"reach": 0,
"result_event": "custom",
"result_event_name": "booked_detail",
"results": 63,
"return_on_ad_spend": 19.62419113987058,
"schedule_value": 640,
"schedules": 46,
"spend": 482.16,
"spend_currency": "usd",
"status": "paused",
"submitted_application_value": 300,
"submitted_applications": 12,
"title": "Ceramic coating — new ad group",
"unique_click_through_rate": null,
"unique_clicks": 1012,
"updated_at": "2026-01-01T12:00:00.000Z",
"url": "https://shinetime.example/ceramic-coating",
"url_parameters": {},
"viewed_content_value": 0,
"viewed_contents": 2140,
"lead_form": {
"completion": {
"button_text": "See our work",
"button_type": "website",
"description": "We book coatings two days out, so pick a slot when we ring.",
"file_url": null,
"headline": "Thanks — we will call you today",
"phone_number": null,
"url": "https://shinetime.example/gallery"
},
"disclaimer": {
"body": "Ceramic coating requires the vehicle for two days.",
"checkboxes": [
{
"checked_by_default": false,
"key": "consent_1",
"required": true,
"text": "I agree"
}
],
"title": "Before you submit"
},
"form_type": "higher_intent",
"intro": {
"description": "Tell us about your vehicle and we will send a fixed price.",
"headline": "Get a ceramic coating quote"
},
"name": "Ceramic coating quote requests",
"phone_verification": true,
"privacy_policy": {
"link_text": "Shine Time privacy policy",
"url": "https://shinetime.example/privacy"
},
"questions": [
{
"type": "custom",
"format": "multiple_choice",
"label": "What vehicle are we detailing?",
"options": [
{
"value": "Ceramic coating",
"key": "ceramic_coating",
"logic": {
"action": "go_to_question",
"target_end_page_index": 0,
"target_question_index": 3
}
}
]
}
]
},
"lead_form_id": "1037724182084885",
"messaging_config": {
"keyword": "Book an interior deep clean",
"message": "Hi! Tell us your vehicle and ZIP and we will send an interior deep clean quote."
},
"multi_advertiser_ads": true,
"music": {
"id": "<string>",
"name": "<string>",
"url": "<string>"
},
"translations": {
"automatic_languages": [
"fr"
],
"source_language": "en"
}
}
]
}{
"error": {
"message": "account_id is required",
"type": "bad_request",
"code": "<string>"
}
}{
"error": {
"message": "account_id is required",
"type": "bad_request",
"code": "<string>"
}
}{
"error": {
"message": "account_id is required",
"type": "bad_request",
"code": "<string>"
}
}Duplicate an Ad
Copies an ad into its own ad group or into another one. Copies keep the source ad’s active or paused state.
import { WhopClient } from "@whop/sdk";
const client = new WhopClient({ token: "YOUR_TOKEN", apiVersionDate: "2026-08-21-1", idempotencyKey: "YOUR_IDEMPOTENCY_KEY" });
await client.ads.duplicate({
id: "id"
});curl --request POST \
--url https://api.whop.com/api/v1/ads/{id}/duplicate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"count": 2,
"preserve_engagement": true,
"target_ad_group_id": "adgrp_xxxxxxxxxxxxxx"
}
'import requests
url = "https://api.whop.com/api/v1/ads/{id}/duplicate"
payload = {
"count": 2,
"preserve_engagement": True,
"target_ad_group_id": "adgrp_xxxxxxxxxxxxxx"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
count: 2,
preserve_engagement: true,
target_ad_group_id: 'adgrp_xxxxxxxxxxxxxx'
})
};
fetch('https://api.whop.com/api/v1/ads/{id}/duplicate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.whop.com/api/v1/ads/{id}/duplicate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'count' => 2,
'preserve_engagement' => true,
'target_ad_group_id' => 'adgrp_xxxxxxxxxxxxxx'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.whop.com/api/v1/ads/{id}/duplicate"
payload := strings.NewReader("{\n \"count\": 2,\n \"preserve_engagement\": true,\n \"target_ad_group_id\": \"adgrp_xxxxxxxxxxxxxx\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.whop.com/api/v1/ads/{id}/duplicate")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"count\": 2,\n \"preserve_engagement\": true,\n \"target_ad_group_id\": \"adgrp_xxxxxxxxxxxxxx\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.whop.com/api/v1/ads/{id}/duplicate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"count\": 2,\n \"preserve_engagement\": true,\n \"target_ad_group_id\": \"adgrp_xxxxxxxxxxxxxx\"\n}"
response = http.request(request)
puts response.read_body{
"data": [
{
"ad_campaign": {
"id": "adcamp_xxxxxxxxxxxxxx"
},
"ad_group": {
"id": "adcamp_xxxxxxxxxxxxxx"
},
"added_to_cart_value": 1850,
"added_to_carts": 74,
"call_to_action": "learn_more",
"click_through_rate": 0.0261,
"clicks": 1337,
"completed_registration_value": 95,
"completed_registrations": 19,
"contact_value": 175,
"contacts": 27,
"cost_per_added_to_cart": 6.515675675675676,
"cost_per_click": 0.36,
"cost_per_completed_registration": 25.37684210526316,
"cost_per_contact": 17.85777777777778,
"cost_per_lead": 3.984793388429752,
"cost_per_mille": 9.42,
"cost_per_purchase": 12.68842105263158,
"cost_per_result": 7.653333333333333,
"cost_per_schedule": 10.48173913043478,
"cost_per_submitted_application": 40.18,
"cost_per_unique_click": 0.48,
"cost_per_viewed_content": 0.2253084112149533,
"created_at": "2026-01-01T12:00:00.000Z",
"creatives": [
{
"crop": {
"height": 1000,
"width": 1000,
"x": 40,
"y": 0
},
"format": "vertical",
"id": "file_xxxxxxxxxxxxxx",
"language": null,
"media_type": "image",
"url": "https://whop-assets-example.s3.amazonaws.com/uploads/image/2026-01-01/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
],
"custom_conversions": 63,
"custom_event_counts": {
"booked_detail": 63
},
"custom_event_values": {
"booked_detail": 4820
},
"delivery_status": "paused",
"descriptions": [
{
"language": "en",
"text": "Austin's mobile detailers"
}
],
"existing_post_id": "xxxxxxxxxxxxxxxx_98765",
"external_accounts": [
{
"id": "adcamp_xxxxxxxxxxxxxx"
}
],
"frequency": null,
"headlines": [
{
"language": "en",
"text": "Austin's mobile detailers"
}
],
"id": "ad_xxxxxxxxxxxxxx",
"impressions": 51204,
"issues": [
{
"category": "error",
"id": "adiss_xxxxxxxxxxxxxx",
"message": "Your ad was rejected for unacceptable business practices. Edit the ad's content and resubmit.",
"resource_id": "ad_xxxxxxxxxxxxxx",
"resource_type": "ad",
"title": "Ad issue"
}
],
"lead_value": 1180,
"leads": 121,
"link_clicks": 1071,
"platform": "meta",
"post_id": "xxxxxxxxxxxxxxxx_98765",
"post_source": "facebook",
"post_thumbnail_url": "https://whop-assets-example.s3.amazonaws.com/uploads/image/2026-01-01/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"primary_texts": [
{
"language": "en",
"text": "Austin's mobile detailers"
}
],
"purchase_value": 9462,
"purchases": 38,
"reach": 0,
"result_event": "custom",
"result_event_name": "booked_detail",
"results": 63,
"return_on_ad_spend": 19.62419113987058,
"schedule_value": 640,
"schedules": 46,
"spend": 482.16,
"spend_currency": "usd",
"status": "paused",
"submitted_application_value": 300,
"submitted_applications": 12,
"title": "Ceramic coating — new ad group",
"unique_click_through_rate": null,
"unique_clicks": 1012,
"updated_at": "2026-01-01T12:00:00.000Z",
"url": "https://shinetime.example/ceramic-coating",
"url_parameters": {},
"viewed_content_value": 0,
"viewed_contents": 2140,
"lead_form": {
"completion": {
"button_text": "See our work",
"button_type": "website",
"description": "We book coatings two days out, so pick a slot when we ring.",
"file_url": null,
"headline": "Thanks — we will call you today",
"phone_number": null,
"url": "https://shinetime.example/gallery"
},
"disclaimer": {
"body": "Ceramic coating requires the vehicle for two days.",
"checkboxes": [
{
"checked_by_default": false,
"key": "consent_1",
"required": true,
"text": "I agree"
}
],
"title": "Before you submit"
},
"form_type": "higher_intent",
"intro": {
"description": "Tell us about your vehicle and we will send a fixed price.",
"headline": "Get a ceramic coating quote"
},
"name": "Ceramic coating quote requests",
"phone_verification": true,
"privacy_policy": {
"link_text": "Shine Time privacy policy",
"url": "https://shinetime.example/privacy"
},
"questions": [
{
"type": "custom",
"format": "multiple_choice",
"label": "What vehicle are we detailing?",
"options": [
{
"value": "Ceramic coating",
"key": "ceramic_coating",
"logic": {
"action": "go_to_question",
"target_end_page_index": 0,
"target_question_index": 3
}
}
]
}
]
},
"lead_form_id": "1037724182084885",
"messaging_config": {
"keyword": "Book an interior deep clean",
"message": "Hi! Tell us your vehicle and ZIP and we will send an interior deep clean quote."
},
"multi_advertiser_ads": true,
"music": {
"id": "<string>",
"name": "<string>",
"url": "<string>"
},
"translations": {
"automatic_languages": [
"fr"
],
"source_language": "en"
}
}
]
}{
"error": {
"message": "account_id is required",
"type": "bad_request",
"code": "<string>"
}
}{
"error": {
"message": "account_id is required",
"type": "bad_request",
"code": "<string>"
}
}{
"error": {
"message": "account_id is required",
"type": "bad_request",
"code": "<string>"
}
}Authorizations
An Account API key, an App API key, an account access token, an account-scoped user token, or a user OAuth token. Prepend the key or token with Bearer, for example Bearer ***************************. See Auth & API keys for how to get each one.
Headers
A unique key that makes this request safe to retry. See Idempotent requests.
255"d9105228-4a08-46b1-8b91-42fed586d383"
Pins the request to a dated API version.
"2026-10-09-2"
Path Parameters
The ad ID.
Body
Number of copies to create (1-10). Defaults to 1.
2
Whether the copies keep the original post's engagement (likes, comments, shares). Defaults to false.
true
Ad group to duplicate into, which must belong to the same account and be compatible with the ad. Defaults to the ad's own ad group.
"adgrp_xxxxxxxxxxxxxx"
Response
ad duplicated
Show child attributes
Show child attributes

