curl --request POST \
--url https://api.onesignal.com/apps/{app_id}/journeys/{id}/duplicate \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"overrides": {
"name": "Welcome series v2"
}
}
'import Onesignal from '@onesignal/node-onesignal';
const configuration = Onesignal.createConfiguration({
restApiKey: 'YOUR_REST_API_KEY',
});
const apiInstance = new Onesignal.DefaultApi(configuration);
// string | Your OneSignal App ID in UUID v4 format.
const appId: string = "YOUR_APP_ID";
// string | UUID of the journey to copy.
const journeyId: string = "YOUR_JOURNEY_ID";
// DuplicateJourneyRequest (optional)
const duplicateJourneyRequest: Onesignal.DuplicateJourneyRequest = {
overrides: {
name: "Welcome series v2",
},
};
try {
const response = await apiInstance.duplicateJourney(appId, journeyId, duplicateJourneyRequest);
console.log(response);
} catch (e) {
if (e instanceof Onesignal.ApiException) {
// `e.errorMessages` flattens any error-envelope shape to a `string[]`;
// the raw parsed body remains on `e.body`.
console.error("duplicateJourney failed: HTTP " + e.code, e.errorMessages);
} else {
throw e;
}
}import onesignal
from onesignal.api import default_api
from onesignal.models import *
from pprint import pprint
# See configuration.py for a list of all supported configuration parameters.
# Some of the OneSignal endpoints require ORGANIZATION_API_KEY token for authorization, while others require REST_API_KEY.
# We recommend adding both of them in the configuration page so that you will not need to figure it out yourself.
configuration = onesignal.Configuration(
rest_api_key = "YOUR_REST_API_KEY", # App REST API key required for most endpoints
organization_api_key = "YOUR_ORGANIZATION_API_KEY" # Organization key is only required for creating new apps and other top-level endpoints
)
# Enter a context with an instance of the API client
with onesignal.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = default_api.DefaultApi(api_client)
app_id = "YOUR_APP_ID" # Your OneSignal App ID in UUID v4 format.
journey_id = "YOUR_JOURNEY_ID" # UUID of the journey to copy.
duplicate_journey_request = DuplicateJourneyRequest(
overrides=DuplicateJourneyOverrides(
name="Welcome series v2",
),
)
try:
# Duplicate journey
api_response = api_instance.duplicate_journey(app_id, journey_id, duplicate_journey_request=duplicate_journey_request)
pprint(api_response)
except onesignal.ApiException as e:
print("Exception when calling DefaultApi->duplicate_journey: %s\n" % e)
print("Status Code: %s" % e.status)
print("Response Body: %s" % e.body)<?php
require_once(__DIR__ . '/vendor/autoload.php');
// Configure Bearer authorization: rest_api_key
$config = onesignal\client\Configuration::getDefaultConfiguration()
->setRestApiKeyToken('YOUR_REST_API_KEY')
->setOrganizationApiKeyToken('YOUR_ORGANIZATION_API_KEY');
$apiInstance = new onesignal\client\Api\DefaultApi(
// If you want use custom http client, pass your client which implements `GuzzleHttp\ClientInterface`.
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client(),
$config
);
$app_id = 'YOUR_APP_ID'; // string | Your OneSignal App ID in UUID v4 format.
$journey_id = 'YOUR_JOURNEY_ID'; // string | UUID of the journey to copy.
$duplicate_journey_request = new \onesignal\client\model\DuplicateJourneyRequest(); // \onesignal\client\model\DuplicateJourneyRequest
try {
$result = $apiInstance->duplicateJourney($app_id, $journey_id, $duplicate_journey_request);
print_r($result);
} catch (\onesignal\client\ApiException $e) {
echo 'Exception when calling DefaultApi->duplicateJourney: ', $e->getMessage(), PHP_EOL;
echo 'Status Code: ', $e->getCode(), PHP_EOL;
// getErrorMessages() flattens any error-envelope shape to a string[];
// the raw body remains on getResponseBody().
echo 'Error Messages: ', implode(', ', $e->getErrorMessages()), PHP_EOL;
echo 'Response Body: ', $e->getResponseBody(), PHP_EOL;
} catch (\Exception $e) {
echo 'Exception when calling DefaultApi->duplicateJourney: ', $e->getMessage(), PHP_EOL;
}package main
import (
"context"
"fmt"
"os"
"github.com/OneSignal/onesignal-go-api/v5"
)
func main() {
appId := "YOUR_APP_ID" // string | Your OneSignal App ID in UUID v4 format.
journeyId := "YOUR_JOURNEY_ID" // string | UUID of the journey to copy.
duplicateJourneyRequest := *onesignal.NewDuplicateJourneyRequest() // DuplicateJourneyRequest | (optional)
configuration := onesignal.NewConfiguration()
apiClient := onesignal.NewAPIClient(configuration)
restAuth := context.WithValue(context.Background(), onesignal.RestApiKey, "YOUR_REST_API_KEY") // App REST API key required for most endpoints
resp, r, err := apiClient.DefaultApi.DuplicateJourney(restAuth, appId, journeyId).DuplicateJourneyRequest(duplicateJourneyRequest).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.DuplicateJourney`: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
if apiErr, ok := err.(*onesignal.GenericOpenAPIError); ok {
// ErrorMessages() flattens any error-envelope shape to a []string;
// the raw body remains on Body().
fmt.Fprintf(os.Stderr, "Error Messages: %v\n", apiErr.ErrorMessages())
fmt.Fprintf(os.Stderr, "Response Body: %s\n", apiErr.Body())
}
}
// response from `DuplicateJourney`: Journey
fmt.Fprintf(os.Stdout, "Response from `DefaultApi.DuplicateJourney`: %v\n", resp)
}require 'onesignal'
# setup authorization
OneSignal.configure do |config|
# Configure Bearer authorization: rest_api_key
config.rest_api_key = 'YOUR_REST_API_KEY'
end
api_instance = OneSignal::DefaultApi.new
app_id = 'YOUR_APP_ID' # String | Your OneSignal App ID in UUID v4 format.
journey_id = 'YOUR_JOURNEY_ID' # String | UUID of the journey to copy.
opts = {
duplicate_journey_request: OneSignal::DuplicateJourneyRequest.new # DuplicateJourneyRequest |
}
begin
# Duplicate journey
result = api_instance.duplicate_journey(app_id, journey_id, opts)
p result
rescue OneSignal::ApiError => e
puts "Error when calling DefaultApi->duplicate_journey: #{e}"
puts "Status Code: #{e.code}"
# `e.error_messages` flattens any error-envelope shape to an Array<String>;
# the raw body remains on `e.response_body`.
puts "Error Messages: #{e.error_messages}"
puts "Response Body: #{e.response_body}"
end// Import classes:
import com.onesignal.client.ApiClient;
import com.onesignal.client.ApiException;
import com.onesignal.client.Configuration;
import com.onesignal.client.auth.*;
import com.onesignal.client.model.*;
import com.onesignal.client.api.DefaultApi;
public class Example {
public static void main(String[] args) {
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath("https://api.onesignal.com");
// Configure HTTP bearer authorization: rest_api_key
HttpBearerAuth rest_api_key = (HttpBearerAuth) defaultClient.getAuthentication("rest_api_key");
rest_api_key.setBearerToken("YOUR_REST_API_KEY");
DefaultApi apiInstance = new DefaultApi(defaultClient);
String appId = "YOUR_APP_ID"; // String | Your OneSignal App ID in UUID v4 format.
String journeyId = "YOUR_JOURNEY_ID"; // String | UUID of the journey to copy.
DuplicateJourneyRequest duplicateJourneyRequest = new DuplicateJourneyRequest(); // DuplicateJourneyRequest |
try {
Journey result = apiInstance.duplicateJourney(appId, journeyId, duplicateJourneyRequest);
System.out.println(result);
} catch (ApiException e) {
System.err.println("Exception when calling DefaultApi#duplicateJourney");
System.err.println("Status code: " + e.getCode());
// getErrorMessages() flattens any error-envelope shape to a List<String>;
// the raw body remains on getResponseBody().
System.err.println("Error messages: " + e.getErrorMessages());
System.err.println("Reason: " + e.getResponseBody());
System.err.println("Response headers: " + e.getResponseHeaders());
e.printStackTrace();
}
}
}using System;
using System.Collections.Generic;
using System.Diagnostics;
using OneSignalApi.Api;
using OneSignalApi.Client;
using OneSignalApi.Model;
namespace Example
{
public class DuplicateJourneyExample
{
public static void Main()
{
Configuration config = new Configuration();
config.BasePath = "https://api.onesignal.com";
// Configure Bearer token for authorization: rest_api_key
config.AccessToken = "YOUR_REST_API_KEY";
var apiInstance = new DefaultApi(config);
var appId = "YOUR_APP_ID"; // string | Your OneSignal App ID in UUID v4 format.
var journeyId = "YOUR_JOURNEY_ID"; // string | UUID of the journey to copy.
var duplicateJourneyRequest = new DuplicateJourneyRequest(); // DuplicateJourneyRequest | (optional)
try
{
// Duplicate journey
Journey result = apiInstance.DuplicateJourney(appId, journeyId, duplicateJourneyRequest);
Debug.WriteLine(result);
}
catch (ApiException e)
{
Debug.Print("Exception when calling DefaultApi.DuplicateJourney: " + e.Message );
Debug.Print("Status Code: "+ e.ErrorCode);
// e.ErrorMessages flattens any error-envelope shape to an IReadOnlyList<string>;
// the raw body remains on e.ErrorContent.
Debug.Print("Error Messages: " + string.Join(", ", e.ErrorMessages));
Debug.Print("Response Body: " + e.ErrorContent);
Debug.Print(e.StackTrace);
}
}
}
}use onesignal_rust_api::apis::configuration::Configuration;
use onesignal_rust_api::apis::default_api;
use onesignal_rust_api::models;
#[tokio::main]
async fn main() {
let mut configuration = Configuration::new();
configuration.rest_api_key_token = Some("YOUR_REST_API_KEY".to_string());
// Realistic values are pulled from the spec's `example:` fields where present.
let app_id: &str = "YOUR_APP_ID";
let journey_id: &str = "YOUR_JOURNEY_ID";
let duplicate_journey_request: Option<models::DuplicateJourneyRequest> = None;
match default_api::duplicate_journey(&configuration, app_id, journey_id, duplicate_journey_request).await {
Ok(resp) => println!("{:?}", resp),
Err(e @ onesignal_rust_api::apis::Error::ResponseError(_)) => {
// `e.error_messages()` flattens any error-envelope shape to a Vec<String>;
// the raw response remains on the ResponseError variant.
eprintln!("duplicate_journey failed: {:?}", e.error_messages());
}
Err(e) => eprintln!("duplicate_journey failed: {:?}", e),
}
}{
"id": "7b6a5948-3726-1504-f3e2-d1c0b9a8f7e6",
"app_id": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",
"name": "Welcome series v2",
"description": "Onboard new users over their first week.",
"state": "draft",
"created_at": "2026-06-02T09:30:00Z",
"updated_at": "2026-06-02T09:30:00Z",
"started_at": null,
"archived_at": null,
"created_source": "public_api",
"audience": {
"kind": "segment",
"included_segment_ids": [
"3f7c1e90-0000-0000-0000-000000000001"
],
"excluded_segment_ids": [],
"future_additions_only": false
},
"early_exit": null,
"reentry_rules": null,
"schedule": null,
"nodes": [
{
"id": "22222222-0000-0000-0000-000000000001",
"kind": "send_push",
"template_id": "9a8b7c6d-0000-0000-0000-000000000001"
},
{
"id": "22222222-0000-0000-0000-000000000002",
"kind": "wait",
"duration_seconds": 86400
},
{
"id": "22222222-0000-0000-0000-000000000003",
"kind": "send_email",
"template_id": "9a8b7c6d-0000-0000-0000-000000000002"
}
],
"concurrency_key": "5b1f0c1d9a3e47a2b8c6d0e4f7a913b5c2d8e6f40a7b9c1d3e5f70829a4b6c8d"
}{
"errors": [
{
"code": "invalid-payload",
"title": "The property '#/nodes/1' contains additional properties [\"id\"] outside of the schema when none are allowed",
"meta": {
"attribute": "base",
"path": "nodes/1"
}
}
]
}{
"errors": [
{
"code": "journey-not-entitled",
"title": "Journeys are not enabled for this app",
"meta": {}
}
]
}{
"errors": [
{
"code": "journey-not-found",
"title": "Journey not found",
"meta": {}
}
]
}{
"errors": [
"API rate limit exceeded."
]
}Duplicate journey
Copy an existing journey into a new draft. You can apply overrides to the copy in the same request.
curl --request POST \
--url https://api.onesignal.com/apps/{app_id}/journeys/{id}/duplicate \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"overrides": {
"name": "Welcome series v2"
}
}
'import Onesignal from '@onesignal/node-onesignal';
const configuration = Onesignal.createConfiguration({
restApiKey: 'YOUR_REST_API_KEY',
});
const apiInstance = new Onesignal.DefaultApi(configuration);
// string | Your OneSignal App ID in UUID v4 format.
const appId: string = "YOUR_APP_ID";
// string | UUID of the journey to copy.
const journeyId: string = "YOUR_JOURNEY_ID";
// DuplicateJourneyRequest (optional)
const duplicateJourneyRequest: Onesignal.DuplicateJourneyRequest = {
overrides: {
name: "Welcome series v2",
},
};
try {
const response = await apiInstance.duplicateJourney(appId, journeyId, duplicateJourneyRequest);
console.log(response);
} catch (e) {
if (e instanceof Onesignal.ApiException) {
// `e.errorMessages` flattens any error-envelope shape to a `string[]`;
// the raw parsed body remains on `e.body`.
console.error("duplicateJourney failed: HTTP " + e.code, e.errorMessages);
} else {
throw e;
}
}import onesignal
from onesignal.api import default_api
from onesignal.models import *
from pprint import pprint
# See configuration.py for a list of all supported configuration parameters.
# Some of the OneSignal endpoints require ORGANIZATION_API_KEY token for authorization, while others require REST_API_KEY.
# We recommend adding both of them in the configuration page so that you will not need to figure it out yourself.
configuration = onesignal.Configuration(
rest_api_key = "YOUR_REST_API_KEY", # App REST API key required for most endpoints
organization_api_key = "YOUR_ORGANIZATION_API_KEY" # Organization key is only required for creating new apps and other top-level endpoints
)
# Enter a context with an instance of the API client
with onesignal.ApiClient(configuration) as api_client:
# Create an instance of the API class
api_instance = default_api.DefaultApi(api_client)
app_id = "YOUR_APP_ID" # Your OneSignal App ID in UUID v4 format.
journey_id = "YOUR_JOURNEY_ID" # UUID of the journey to copy.
duplicate_journey_request = DuplicateJourneyRequest(
overrides=DuplicateJourneyOverrides(
name="Welcome series v2",
),
)
try:
# Duplicate journey
api_response = api_instance.duplicate_journey(app_id, journey_id, duplicate_journey_request=duplicate_journey_request)
pprint(api_response)
except onesignal.ApiException as e:
print("Exception when calling DefaultApi->duplicate_journey: %s\n" % e)
print("Status Code: %s" % e.status)
print("Response Body: %s" % e.body)<?php
require_once(__DIR__ . '/vendor/autoload.php');
// Configure Bearer authorization: rest_api_key
$config = onesignal\client\Configuration::getDefaultConfiguration()
->setRestApiKeyToken('YOUR_REST_API_KEY')
->setOrganizationApiKeyToken('YOUR_ORGANIZATION_API_KEY');
$apiInstance = new onesignal\client\Api\DefaultApi(
// If you want use custom http client, pass your client which implements `GuzzleHttp\ClientInterface`.
// This is optional, `GuzzleHttp\Client` will be used as default.
new GuzzleHttp\Client(),
$config
);
$app_id = 'YOUR_APP_ID'; // string | Your OneSignal App ID in UUID v4 format.
$journey_id = 'YOUR_JOURNEY_ID'; // string | UUID of the journey to copy.
$duplicate_journey_request = new \onesignal\client\model\DuplicateJourneyRequest(); // \onesignal\client\model\DuplicateJourneyRequest
try {
$result = $apiInstance->duplicateJourney($app_id, $journey_id, $duplicate_journey_request);
print_r($result);
} catch (\onesignal\client\ApiException $e) {
echo 'Exception when calling DefaultApi->duplicateJourney: ', $e->getMessage(), PHP_EOL;
echo 'Status Code: ', $e->getCode(), PHP_EOL;
// getErrorMessages() flattens any error-envelope shape to a string[];
// the raw body remains on getResponseBody().
echo 'Error Messages: ', implode(', ', $e->getErrorMessages()), PHP_EOL;
echo 'Response Body: ', $e->getResponseBody(), PHP_EOL;
} catch (\Exception $e) {
echo 'Exception when calling DefaultApi->duplicateJourney: ', $e->getMessage(), PHP_EOL;
}package main
import (
"context"
"fmt"
"os"
"github.com/OneSignal/onesignal-go-api/v5"
)
func main() {
appId := "YOUR_APP_ID" // string | Your OneSignal App ID in UUID v4 format.
journeyId := "YOUR_JOURNEY_ID" // string | UUID of the journey to copy.
duplicateJourneyRequest := *onesignal.NewDuplicateJourneyRequest() // DuplicateJourneyRequest | (optional)
configuration := onesignal.NewConfiguration()
apiClient := onesignal.NewAPIClient(configuration)
restAuth := context.WithValue(context.Background(), onesignal.RestApiKey, "YOUR_REST_API_KEY") // App REST API key required for most endpoints
resp, r, err := apiClient.DefaultApi.DuplicateJourney(restAuth, appId, journeyId).DuplicateJourneyRequest(duplicateJourneyRequest).Execute()
if err != nil {
fmt.Fprintf(os.Stderr, "Error when calling `DefaultApi.DuplicateJourney`: %v\n", err)
fmt.Fprintf(os.Stderr, "Full HTTP response: %v\n", r)
if apiErr, ok := err.(*onesignal.GenericOpenAPIError); ok {
// ErrorMessages() flattens any error-envelope shape to a []string;
// the raw body remains on Body().
fmt.Fprintf(os.Stderr, "Error Messages: %v\n", apiErr.ErrorMessages())
fmt.Fprintf(os.Stderr, "Response Body: %s\n", apiErr.Body())
}
}
// response from `DuplicateJourney`: Journey
fmt.Fprintf(os.Stdout, "Response from `DefaultApi.DuplicateJourney`: %v\n", resp)
}require 'onesignal'
# setup authorization
OneSignal.configure do |config|
# Configure Bearer authorization: rest_api_key
config.rest_api_key = 'YOUR_REST_API_KEY'
end
api_instance = OneSignal::DefaultApi.new
app_id = 'YOUR_APP_ID' # String | Your OneSignal App ID in UUID v4 format.
journey_id = 'YOUR_JOURNEY_ID' # String | UUID of the journey to copy.
opts = {
duplicate_journey_request: OneSignal::DuplicateJourneyRequest.new # DuplicateJourneyRequest |
}
begin
# Duplicate journey
result = api_instance.duplicate_journey(app_id, journey_id, opts)
p result
rescue OneSignal::ApiError => e
puts "Error when calling DefaultApi->duplicate_journey: #{e}"
puts "Status Code: #{e.code}"
# `e.error_messages` flattens any error-envelope shape to an Array<String>;
# the raw body remains on `e.response_body`.
puts "Error Messages: #{e.error_messages}"
puts "Response Body: #{e.response_body}"
end// Import classes:
import com.onesignal.client.ApiClient;
import com.onesignal.client.ApiException;
import com.onesignal.client.Configuration;
import com.onesignal.client.auth.*;
import com.onesignal.client.model.*;
import com.onesignal.client.api.DefaultApi;
public class Example {
public static void main(String[] args) {
ApiClient defaultClient = Configuration.getDefaultApiClient();
defaultClient.setBasePath("https://api.onesignal.com");
// Configure HTTP bearer authorization: rest_api_key
HttpBearerAuth rest_api_key = (HttpBearerAuth) defaultClient.getAuthentication("rest_api_key");
rest_api_key.setBearerToken("YOUR_REST_API_KEY");
DefaultApi apiInstance = new DefaultApi(defaultClient);
String appId = "YOUR_APP_ID"; // String | Your OneSignal App ID in UUID v4 format.
String journeyId = "YOUR_JOURNEY_ID"; // String | UUID of the journey to copy.
DuplicateJourneyRequest duplicateJourneyRequest = new DuplicateJourneyRequest(); // DuplicateJourneyRequest |
try {
Journey result = apiInstance.duplicateJourney(appId, journeyId, duplicateJourneyRequest);
System.out.println(result);
} catch (ApiException e) {
System.err.println("Exception when calling DefaultApi#duplicateJourney");
System.err.println("Status code: " + e.getCode());
// getErrorMessages() flattens any error-envelope shape to a List<String>;
// the raw body remains on getResponseBody().
System.err.println("Error messages: " + e.getErrorMessages());
System.err.println("Reason: " + e.getResponseBody());
System.err.println("Response headers: " + e.getResponseHeaders());
e.printStackTrace();
}
}
}using System;
using System.Collections.Generic;
using System.Diagnostics;
using OneSignalApi.Api;
using OneSignalApi.Client;
using OneSignalApi.Model;
namespace Example
{
public class DuplicateJourneyExample
{
public static void Main()
{
Configuration config = new Configuration();
config.BasePath = "https://api.onesignal.com";
// Configure Bearer token for authorization: rest_api_key
config.AccessToken = "YOUR_REST_API_KEY";
var apiInstance = new DefaultApi(config);
var appId = "YOUR_APP_ID"; // string | Your OneSignal App ID in UUID v4 format.
var journeyId = "YOUR_JOURNEY_ID"; // string | UUID of the journey to copy.
var duplicateJourneyRequest = new DuplicateJourneyRequest(); // DuplicateJourneyRequest | (optional)
try
{
// Duplicate journey
Journey result = apiInstance.DuplicateJourney(appId, journeyId, duplicateJourneyRequest);
Debug.WriteLine(result);
}
catch (ApiException e)
{
Debug.Print("Exception when calling DefaultApi.DuplicateJourney: " + e.Message );
Debug.Print("Status Code: "+ e.ErrorCode);
// e.ErrorMessages flattens any error-envelope shape to an IReadOnlyList<string>;
// the raw body remains on e.ErrorContent.
Debug.Print("Error Messages: " + string.Join(", ", e.ErrorMessages));
Debug.Print("Response Body: " + e.ErrorContent);
Debug.Print(e.StackTrace);
}
}
}
}use onesignal_rust_api::apis::configuration::Configuration;
use onesignal_rust_api::apis::default_api;
use onesignal_rust_api::models;
#[tokio::main]
async fn main() {
let mut configuration = Configuration::new();
configuration.rest_api_key_token = Some("YOUR_REST_API_KEY".to_string());
// Realistic values are pulled from the spec's `example:` fields where present.
let app_id: &str = "YOUR_APP_ID";
let journey_id: &str = "YOUR_JOURNEY_ID";
let duplicate_journey_request: Option<models::DuplicateJourneyRequest> = None;
match default_api::duplicate_journey(&configuration, app_id, journey_id, duplicate_journey_request).await {
Ok(resp) => println!("{:?}", resp),
Err(e @ onesignal_rust_api::apis::Error::ResponseError(_)) => {
// `e.error_messages()` flattens any error-envelope shape to a Vec<String>;
// the raw response remains on the ResponseError variant.
eprintln!("duplicate_journey failed: {:?}", e.error_messages());
}
Err(e) => eprintln!("duplicate_journey failed: {:?}", e),
}
}{
"id": "7b6a5948-3726-1504-f3e2-d1c0b9a8f7e6",
"app_id": "1a2b3c4d-5e6f-7081-92a3-b4c5d6e7f809",
"name": "Welcome series v2",
"description": "Onboard new users over their first week.",
"state": "draft",
"created_at": "2026-06-02T09:30:00Z",
"updated_at": "2026-06-02T09:30:00Z",
"started_at": null,
"archived_at": null,
"created_source": "public_api",
"audience": {
"kind": "segment",
"included_segment_ids": [
"3f7c1e90-0000-0000-0000-000000000001"
],
"excluded_segment_ids": [],
"future_additions_only": false
},
"early_exit": null,
"reentry_rules": null,
"schedule": null,
"nodes": [
{
"id": "22222222-0000-0000-0000-000000000001",
"kind": "send_push",
"template_id": "9a8b7c6d-0000-0000-0000-000000000001"
},
{
"id": "22222222-0000-0000-0000-000000000002",
"kind": "wait",
"duration_seconds": 86400
},
{
"id": "22222222-0000-0000-0000-000000000003",
"kind": "send_email",
"template_id": "9a8b7c6d-0000-0000-0000-000000000002"
}
],
"concurrency_key": "5b1f0c1d9a3e47a2b8c6d0e4f7a913b5c2d8e6f40a7b9c1d3e5f70829a4b6c8d"
}{
"errors": [
{
"code": "invalid-payload",
"title": "The property '#/nodes/1' contains additional properties [\"id\"] outside of the schema when none are allowed",
"meta": {
"attribute": "base",
"path": "nodes/1"
}
}
]
}{
"errors": [
{
"code": "journey-not-entitled",
"title": "Journeys are not enabled for this app",
"meta": {}
}
]
}{
"errors": [
{
"code": "journey-not-found",
"title": "Journey not found",
"meta": {}
}
]
}{
"errors": [
"API rate limit exceeded."
]
}Overview
Copy the Journey named in the path. The copy is a new journey in thedraft state. The source does not change.
started_at and archived_at set to null. To activate the copy, use the Update journey API or the OneSignal dashboard.How to use this API
Authenticate with your App API Key. The key must have permission to create journeys. To find theid of the source journey, use the View journeys API. You can also open the journey in the dashboard and read the id from the URL.
POST /apps/{app_id}/journeys/{id}/duplicate
What the copy inherits
The copy carries thedescription, audience, nodes, early_exit, and reentry_rules of the source.
name: the copy takes the name of the source plus(Copy). If the result is longer than the 300 character limit, the source part is truncated to fit. If you duplicate a copy, the suffix is added again rather than counted.nodes: nodes and branches get newidvalues from the server. Aclient_node_idis a value that you assign, so the copy keeps it unchanged. Anon_notification_actioncondition points at the matching node in the copy.schedule: the copy does not inherit it, because a copiedstart_atis almost always in the past. To schedule the copy, send ascheduleunderoverrides.stateisdraft,started_atandarchived_atarenull, andcreated_sourceispublic_api. The copy gets its ownconcurrency_key.
Apply overrides
overrides holds a journey document that is applied over the copy. It uses JSON Merge Patch (RFC 7396), which merges one document into another. overrides accepts the same writable fields as Create journey, and none of them are required. Use it to create the copy in its final state, instead of creating the copy and then patching it.
{
"overrides": {
"name": "Welcome series v2",
"description": null,
"schedule": { "start_at": "2026-07-01T14:00:00Z" },
"early_exit": { "rules": { "on_session": true } }
}
}
- An object merges into the copied object key by key. The
early_exitabove adds a rule and leaves the other rules of the source in place. nullclears the copied value. The"description": nullabove gives the copy no description.- An array replaces the copied array as a unit. A
nodesarray replaces the whole graph of the source and does not merge into it. Send the complete graph that you want. - If an
audienceoverride changeskind, the copy drops the fields of the previous kind. It does not keep them beside the new fields.
400 validation error, exactly as on Create journey. This covers the journey id and state, the lifecycle timestamps, and the id fields on nodes and branches.nodes override replaces the graph, so an id inside it addresses nothing. To reference a node from elsewhere in the same request, use client_node_id.
Only overrides is read from the body. A journey field sent at the top level is ignored rather than rejected. For example, {"name": "Renamed"} leaves the copy with the derived name.
Response
A successful request returns201 Created and the full copy in the draft state. The response contains the id fields from the server and the concurrency_key. On a later Update journey request, pass that concurrency_key unchanged. It stops your request from overwriting a concurrent change.
Error responses
| Status | Code | Description |
|---|---|---|
| 400 | invalid-payload | overrides failed validation. This covers an overrides value that is not an object, schema failures such as a server-controlled field or an unknown property, and business-logic failures on the copy. |
| 403 | journey-not-entitled | Journeys are not enabled for this app. |
| 404 | journey-not-found | No journey with that id exists for this app. |
| 429 | Rate limit exceeded. Wait the number of seconds in the Retry-After header before retrying. See Rate limits. |
{ "errors": [{ "code", "title", "meta" }] }. Branch on code and meta, not on title, whose wording comes from the schema validator and can change between releases.
A schema failure reports base in meta.attribute and a JSON Pointer (RFC 6901) in meta.path. The pointer names the object that holds the offending property, not the property itself, and the same pointer appears in title. Both are relative to the overrides document rather than to the request body. A rejected state at the top of overrides therefore reports #/, and meta.path is omitted because the pointer is the root. A business-logic failure on the copy names the field in meta.attribute instead.Body
Journey fields to apply to the copy. Accepts the same writable fields as Create journey, and none of them are required. Fields sent outside overrides are ignored.
Show child attributes
Show child attributes
Response
201
Full journey representation returned by the detail and create endpoints.
Journey UUID. Read-only.
UUID of the app the journey belongs to. Read-only.
Journey name, up to 300 characters.
Journey description, up to 1024 characters. Defaults to an empty string.
Journey state. Read-only. New journeys are created as draft. processing is a transient state while an activation is in progress, and archived is a journey that has been stopped. Change it through the state field on Update journey.
draft, scheduled, processing, active, archived ISO 8601 creation time. Read-only.
ISO 8601 last-update time. Read-only.
ISO 8601 time the journey was activated, or null. Read-only. May stay null briefly after you set state to active: activation is enqueued for processing, and started_at populates once the journey finishes processing and becomes active.
ISO 8601 time the journey was archived, or null. Read-only.
Origin of the journey, for example public_api or dashboard. Read-only.
The journey entry audience. Either a segment-based or event-triggered audience.
- segment
- event_trigger
Show child attributes
Show child attributes
Conditions that remove a user from the journey before it completes. At least one rule must be set under rules; an early_exit that configures no rule is rejected. Send null to remove early exit entirely, or null for an individual rule to drop just that rule.
Show child attributes
Show child attributes
Controls whether and how soon a user can re-enter the journey. null means re-entry is not allowed.
Show child attributes
Show child attributes
Optional future start and/or stop time. null means no scheduled activation.
Show child attributes
Show child attributes
Ordered list of journey nodes.
A journey node. The kind field selects the shape. Branching nodes (split_range, yes_no, wait_until) nest their sub-graphs inline via branches[].nodes.
- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
- Option 6
- Option 7
- Option 8
- Option 9
Show child attributes
Show child attributes
Opaque optimistic-concurrency token. Read-only. Pass it back on update to guard against overwriting a concurrent change (409 journey-stale). Send it back exactly as read from this response; do not construct or parse it.
Was this page helpful?