Duplicate Journey
curl --request POST \
--url https://api.tracklysms.com/api/v2/journeys/{journey_id}/duplicate \
--header 'X-Api-Key: <api-key>'import requests
url = "https://api.tracklysms.com/api/v2/journeys/{journey_id}/duplicate"
headers = {"X-Api-Key": "<api-key>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {'X-Api-Key': '<api-key>'}};
fetch('https://api.tracklysms.com/api/v2/journeys/{journey_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.tracklysms.com/api/v2/journeys/{journey_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_HTTPHEADER => [
"X-Api-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.tracklysms.com/api/v2/journeys/{journey_id}/duplicate"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("X-Api-Key", "<api-key>")
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.tracklysms.com/api/v2/journeys/{journey_id}/duplicate")
.header("X-Api-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tracklysms.com/api/v2/journeys/{journey_id}/duplicate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Api-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Journey duplicated successfully",
"journey": {
"id": "664f1a2b3c4d5e6f7a8b9c0e",
"accountId": 101,
"name": "Welcome (Copy)",
"trigger": {
"operator": "AND",
"conditions": [],
"groups": []
},
"priority": 0,
"steps": [
{
"stepId": "done",
"name": "Exit",
"stepType": "exit",
"config": {},
"nextStepId": null
}
],
"startStepId": "done",
"status": "draft",
"pauseReason": null,
"createdAt": "2026-09-21T10:00:00",
"updatedAt": "2026-09-21T10:00:00"
}
}
Journeys (v2)
Duplicate Journey
Create a draft copy of an SMS welcome journey.
POST
/
v2
/
journeys
/
{journey_id}
/
duplicate
Duplicate Journey
curl --request POST \
--url https://api.tracklysms.com/api/v2/journeys/{journey_id}/duplicate \
--header 'X-Api-Key: <api-key>'import requests
url = "https://api.tracklysms.com/api/v2/journeys/{journey_id}/duplicate"
headers = {"X-Api-Key": "<api-key>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {'X-Api-Key': '<api-key>'}};
fetch('https://api.tracklysms.com/api/v2/journeys/{journey_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.tracklysms.com/api/v2/journeys/{journey_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_HTTPHEADER => [
"X-Api-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.tracklysms.com/api/v2/journeys/{journey_id}/duplicate"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("X-Api-Key", "<api-key>")
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.tracklysms.com/api/v2/journeys/{journey_id}/duplicate")
.header("X-Api-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tracklysms.com/api/v2/journeys/{journey_id}/duplicate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Api-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"success": true,
"message": "Journey duplicated successfully",
"journey": {
"id": "664f1a2b3c4d5e6f7a8b9c0e",
"accountId": 101,
"name": "Welcome (Copy)",
"trigger": {
"operator": "AND",
"conditions": [],
"groups": []
},
"priority": 0,
"steps": [
{
"stepId": "done",
"name": "Exit",
"stepType": "exit",
"config": {},
"nextStepId": null
}
],
"startStepId": "done",
"status": "draft",
"pauseReason": null,
"createdAt": "2026-09-21T10:00:00",
"updatedAt": "2026-09-21T10:00:00"
}
}
Copies the journey trigger, priority, steps, and start step into a new journey named with a
Invalid copied steps return 400; missing or foreign journeys return 404. Follow the write retry rules.
(Copy) suffix. If appending the suffix to the source name would exceed the 255-character name limit, the source name is truncated so the resulting name fits within 255 characters. The copy always starts as a draft. Steps and their references are validated again.
Declares the journeys.write scope. Authenticate with X-Api-Key; see Authentication.
Duplicating a draft still requires an unrestricted live key under the API key requirements. No request body is required.
Path Parameters
string
required
Journey ID returned by create or list. Use a valid journey ID; malformed IDs currently return an unexpected error.
Response
Thejourney object uses camelCase: id, integer accountId, name, trigger, priority, steps, startStepId, status, pauseReason, createdAt, and updatedAt. The trigger can be null. Each step contains stepId, name, stepType, config, and nullable nextStepId. Pause reasons and absent timestamps can be null.
{
"success": true,
"message": "Journey duplicated successfully",
"journey": {
"id": "664f1a2b3c4d5e6f7a8b9c0e",
"accountId": 101,
"name": "Welcome (Copy)",
"trigger": {
"operator": "AND",
"conditions": [],
"groups": []
},
"priority": 0,
"steps": [
{
"stepId": "done",
"name": "Exit",
"stepType": "exit",
"config": {},
"nextStepId": null
}
],
"startStepId": "done",
"status": "draft",
"pauseReason": null,
"createdAt": "2026-09-21T10:00:00",
"updatedAt": "2026-09-21T10:00:00"
}
}
Idempotency
This endpoint accepts anIdempotency-Key header. Reuse the same key when a lost response leaves the outcome ambiguous: the first 201 is cached for 24 hours and replayed verbatim, so the retry returns the copy the first request created instead of creating a second one. A journey_write_outcome_unknown 409 is cached the same way.
The replay fingerprint includes the source journey path, so the same key sent to a different journey returns 409 idempotency_conflict rather than replaying the earlier copy, and it never collides with a key used for Create Journey. A retry arriving while the first request is still running returns 409 idempotency_in_progress with a Retry-After header. Without the header the request runs unprotected: inspect your journeys before repeating it after a lost response. See Idempotency for the shared rules.