import { randomUUID } from 'node:crypto';
import { Configuration, CompositionsApi, OperationsApi } from '@widgetic/api-sdk-ts';
const config = new Configuration({
apiKey: 'YOUR_API_KEY'
});
const compositionsApi = new CompositionsApi(config);
const operationsApi = new OperationsApi(config);
try {
const started = await compositionsApi.duplicateComposition({
id: 'composition_123',
// The call needs an idempotency key: reuse the same key if you retry this call.
idempotencyKey: randomUUID(),
duplicateCompositionRequest: {
name: 'My Duplicated Composition',
includeDatabase: false
}
});
// The duplicate is made after the answer: ask for the operation until it has finished
const operationId = started.data?.operationId;
if (!operationId) throw new Error('The answer has no operationId');
const finalStatuses = ['completed', 'failed', 'cancelled'];
let operation = await operationsApi.getOperation({ id: operationId });
while (!finalStatuses.includes(operation.data.status ?? '')) {
await new Promise((resolve) => setTimeout(resolve, 1000));
operation = await operationsApi.getOperation({ id: operationId });
}
if (operation.data.status !== 'completed') {
console.error('Duplication did not finish:', operation.data.status, operation.data.error);
} else {
console.log('Duplicated composition:', operation.data.metadata?.newCompositionId);
if (operation.data.metadata?.warnings?.length) {
console.warn('Warnings:', operation.data.metadata.warnings);
}
}
} catch (error) {
if (error.response?.status === 404) {
console.error('Source composition not found');
} else {
console.error('Failed to duplicate composition:', error);
}
}curl --request POST \
--url https://api.widgetic.com/v1/compositions/{id}/duplicate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"name": "My Duplicated Composition",
"includeDatabase": false
}
'import requests
url = "https://api.widgetic.com/v1/compositions/{id}/duplicate"
payload = {
"name": "My Duplicated Composition",
"includeDatabase": False
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({name: 'My Duplicated Composition', includeDatabase: false})
};
fetch('https://api.widgetic.com/v1/compositions/{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.widgetic.com/v1/compositions/{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([
'name' => 'My Duplicated Composition',
'includeDatabase' => false
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-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"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.widgetic.com/v1/compositions/{id}/duplicate"
payload := strings.NewReader("{\n \"name\": \"My Duplicated Composition\",\n \"includeDatabase\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
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.widgetic.com/v1/compositions/{id}/duplicate")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"My Duplicated Composition\",\n \"includeDatabase\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.widgetic.com/v1/compositions/{id}/duplicate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"My Duplicated Composition\",\n \"includeDatabase\": false\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"operationId": "123e4567-e89b-12d3-a456-426614174000"
},
"meta": {
"page": 1,
"size": 20,
"total": 135,
"limit": 20,
"offset": 0,
"hasNextPage": true,
"hasPreviousPage": false,
"nextCursor": "<string>",
"warnings": [
"<string>"
]
}
}{
"success": false,
"message": "<string>",
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Composition not found",
"timestamp": "2026-10-15T10:00:00.000Z",
"param": "name",
"docUrl": "<string>",
"suggestedAction": "<string>",
"retryAfter": 60,
"details": {},
"correlationId": "req_123e4567-e89b-12d3-a456-426614174000"
}
}{
"success": false,
"message": "<string>",
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Composition not found",
"timestamp": "2026-10-15T10:00:00.000Z",
"param": "name",
"docUrl": "<string>",
"suggestedAction": "<string>",
"retryAfter": 60,
"details": {},
"correlationId": "req_123e4567-e89b-12d3-a456-426614174000"
}
}{
"success": false,
"message": "<string>",
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Composition not found",
"timestamp": "2026-10-15T10:00:00.000Z",
"param": "name",
"docUrl": "<string>",
"suggestedAction": "<string>",
"retryAfter": 60,
"details": {},
"correlationId": "req_123e4567-e89b-12d3-a456-426614174000"
}
}Duplicate Composition
Creates a duplicate of an existing composition for the authenticated user.
The duplicate is made after the answer: the call returns 202 with an operationId. Ask
GET /operations/{id} for that operation until its status is completed; the new composition is
named in metadata.newCompositionId. A problem with the duplicate’s database (for example, tables that
could not be made just now) is listed in metadata.warnings, which is absent when there is none.
The Idempotency-Key header is required (400 without it). Send the same key when you retry, so one
duplicate is made, not two.
import { randomUUID } from 'node:crypto';
import { Configuration, CompositionsApi, OperationsApi } from '@widgetic/api-sdk-ts';
const config = new Configuration({
apiKey: 'YOUR_API_KEY'
});
const compositionsApi = new CompositionsApi(config);
const operationsApi = new OperationsApi(config);
try {
const started = await compositionsApi.duplicateComposition({
id: 'composition_123',
// The call needs an idempotency key: reuse the same key if you retry this call.
idempotencyKey: randomUUID(),
duplicateCompositionRequest: {
name: 'My Duplicated Composition',
includeDatabase: false
}
});
// The duplicate is made after the answer: ask for the operation until it has finished
const operationId = started.data?.operationId;
if (!operationId) throw new Error('The answer has no operationId');
const finalStatuses = ['completed', 'failed', 'cancelled'];
let operation = await operationsApi.getOperation({ id: operationId });
while (!finalStatuses.includes(operation.data.status ?? '')) {
await new Promise((resolve) => setTimeout(resolve, 1000));
operation = await operationsApi.getOperation({ id: operationId });
}
if (operation.data.status !== 'completed') {
console.error('Duplication did not finish:', operation.data.status, operation.data.error);
} else {
console.log('Duplicated composition:', operation.data.metadata?.newCompositionId);
if (operation.data.metadata?.warnings?.length) {
console.warn('Warnings:', operation.data.metadata.warnings);
}
}
} catch (error) {
if (error.response?.status === 404) {
console.error('Source composition not found');
} else {
console.error('Failed to duplicate composition:', error);
}
}curl --request POST \
--url https://api.widgetic.com/v1/compositions/{id}/duplicate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"name": "My Duplicated Composition",
"includeDatabase": false
}
'import requests
url = "https://api.widgetic.com/v1/compositions/{id}/duplicate"
payload = {
"name": "My Duplicated Composition",
"includeDatabase": False
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({name: 'My Duplicated Composition', includeDatabase: false})
};
fetch('https://api.widgetic.com/v1/compositions/{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.widgetic.com/v1/compositions/{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([
'name' => 'My Duplicated Composition',
'includeDatabase' => false
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-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"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.widgetic.com/v1/compositions/{id}/duplicate"
payload := strings.NewReader("{\n \"name\": \"My Duplicated Composition\",\n \"includeDatabase\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
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.widgetic.com/v1/compositions/{id}/duplicate")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"My Duplicated Composition\",\n \"includeDatabase\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.widgetic.com/v1/compositions/{id}/duplicate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"My Duplicated Composition\",\n \"includeDatabase\": false\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"operationId": "123e4567-e89b-12d3-a456-426614174000"
},
"meta": {
"page": 1,
"size": 20,
"total": 135,
"limit": 20,
"offset": 0,
"hasNextPage": true,
"hasPreviousPage": false,
"nextCursor": "<string>",
"warnings": [
"<string>"
]
}
}{
"success": false,
"message": "<string>",
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Composition not found",
"timestamp": "2026-10-15T10:00:00.000Z",
"param": "name",
"docUrl": "<string>",
"suggestedAction": "<string>",
"retryAfter": 60,
"details": {},
"correlationId": "req_123e4567-e89b-12d3-a456-426614174000"
}
}{
"success": false,
"message": "<string>",
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Composition not found",
"timestamp": "2026-10-15T10:00:00.000Z",
"param": "name",
"docUrl": "<string>",
"suggestedAction": "<string>",
"retryAfter": 60,
"details": {},
"correlationId": "req_123e4567-e89b-12d3-a456-426614174000"
}
}{
"success": false,
"message": "<string>",
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Composition not found",
"timestamp": "2026-10-15T10:00:00.000Z",
"param": "name",
"docUrl": "<string>",
"suggestedAction": "<string>",
"retryAfter": 60,
"details": {},
"correlationId": "req_123e4567-e89b-12d3-a456-426614174000"
}
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
Required: a call without it is refused with 400. A key of your own that names this one request; a UUID is ideal. It is 8 to 50 characters of letters, digits, - and _. Send the same key again when you retry (after a timeout, a dropped connection or a 5xx) and the call is carried out once: the retry gets the first answer back, marked X-Idempotency-Cached: true. The same key with a different body is refused with 422, and a retry that arrives while the first call is still running with 409 (see Retry-After).
8 - 50^[a-zA-Z0-9_-]{8,50}$"9b2d6f0a-3c41-4e5a-8d77-1f2e3a4b5c6d"
The id Widgetic gave the managed user (POST /managed-users). Makes this call for one of your managed users, so that a mistake in your own code cannot reach another customer's work. Send this header, or the other one, never both. Left out, the call works on your whole account, as it always has. With it, the call reaches only that user's compositions and workflows: a list shows only theirs, a composition or a workflow the call makes is filed under them, and one of another user, or one that does not exist, answers 404 (the same answer, so the call cannot be used to find out what another customer has). A managed user that is not yours answers 404 with MANAGED_USER_NOT_FOUND. An empty, repeated or malformed header is 400 with MANAGED_USER_SCOPE_INVALID; an empty header is never read as no header. A call that is about the whole account, such as the list of your managed users, is refused with 403 and MANAGED_USER_SCOPE_ROUTE_NOT_AVAILABLE: send it without the header. Only your API key can send it. The header is a limit the call asks for: whoever holds the key can leave it out, so for a credential that a customer's browser holds, use an editor session. An Idempotency-Key is kept apart for each managed user.
"3f2b8c1e-7a4d-4e0b-9c55-2d1a6b7e8f90"
Your own id for the managed user (its externalId), when you do not keep Widgetic's. Makes this call for one of your managed users, so that a mistake in your own code cannot reach another customer's work. Send this header, or the other one, never both. Left out, the call works on your whole account, as it always has. With it, the call reaches only that user's compositions and workflows: a list shows only theirs, a composition or a workflow the call makes is filed under them, and one of another user, or one that does not exist, answers 404 (the same answer, so the call cannot be used to find out what another customer has). A managed user that is not yours answers 404 with MANAGED_USER_NOT_FOUND. An empty, repeated or malformed header is 400 with MANAGED_USER_SCOPE_INVALID; an empty header is never read as no header. A call that is about the whole account, such as the list of your managed users, is refused with 403 and MANAGED_USER_SCOPE_ROUTE_NOT_AVAILABLE: send it without the header. Only your API key can send it. The header is a limit the call asks for: whoever holds the key can leave it out, so for a credential that a customer's browser holds, use an editor session. An Idempotency-Key is kept apart for each managed user.
1 - 255"customer-1042"
Path Parameters
ID of the composition to duplicate
Body
Optional new name for the duplicate (defaults to "Copy of [original name]")
"My Duplicated Composition"
Whether the duplicate gets the tables the original declares (databaseSchema), empty and in a schema
of their own (default false). What visitors left in the original is never copied, and the duplicate
never shares the original's data.
Response
Composition duplication initiated successfully