curl --request POST \
--url https://api.parallellabs.app/api/v0/marketing-campaign-templates \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"name": "<string>",
"description": "<string>",
"subject": "<string>",
"replyTo": "<string>",
"bodyDocument": "<string>",
"senderProfileId": "<string>",
"listId": "<string>",
"fieldMappings": {},
"scheduleSettings": "<string>",
"companyId": "<string>"
}
'import requests
url = "https://api.parallellabs.app/api/v0/marketing-campaign-templates"
payload = {
"name": "<string>",
"description": "<string>",
"subject": "<string>",
"replyTo": "<string>",
"bodyDocument": "<string>",
"senderProfileId": "<string>",
"listId": "<string>",
"fieldMappings": {},
"scheduleSettings": "<string>",
"companyId": "<string>"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
description: '<string>',
subject: '<string>',
replyTo: '<string>',
bodyDocument: '<string>',
senderProfileId: '<string>',
listId: '<string>',
fieldMappings: {},
scheduleSettings: '<string>',
companyId: '<string>'
})
};
fetch('https://api.parallellabs.app/api/v0/marketing-campaign-templates', 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.parallellabs.app/api/v0/marketing-campaign-templates",
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' => '<string>',
'description' => '<string>',
'subject' => '<string>',
'replyTo' => '<string>',
'bodyDocument' => '<string>',
'senderProfileId' => '<string>',
'listId' => '<string>',
'fieldMappings' => [
],
'scheduleSettings' => '<string>',
'companyId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"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"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.parallellabs.app/api/v0/marketing-campaign-templates"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"subject\": \"<string>\",\n \"replyTo\": \"<string>\",\n \"bodyDocument\": \"<string>\",\n \"senderProfileId\": \"<string>\",\n \"listId\": \"<string>\",\n \"fieldMappings\": {},\n \"scheduleSettings\": \"<string>\",\n \"companyId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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.parallellabs.app/api/v0/marketing-campaign-templates")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"subject\": \"<string>\",\n \"replyTo\": \"<string>\",\n \"bodyDocument\": \"<string>\",\n \"senderProfileId\": \"<string>\",\n \"listId\": \"<string>\",\n \"fieldMappings\": {},\n \"scheduleSettings\": \"<string>\",\n \"companyId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.parallellabs.app/api/v0/marketing-campaign-templates")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"subject\": \"<string>\",\n \"replyTo\": \"<string>\",\n \"bodyDocument\": \"<string>\",\n \"senderProfileId\": \"<string>\",\n \"listId\": \"<string>\",\n \"fieldMappings\": {},\n \"scheduleSettings\": \"<string>\",\n \"companyId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"companyId": "<string>",
"uid": "<string>",
"name": "<string>",
"description": "<string>",
"subject": "<string>",
"replyTo": "<string>",
"bodyHtml": "<string>",
"bodyDocument": "<string>",
"senderProfileId": "<string>",
"listId": "<string>",
"fieldMappings": {},
"scheduleSettings": "<string>",
"chatHistory": [
"<string>"
],
"created_date": "<string>",
"updated_date": "<string>",
"smartList": {
"id": "<string>",
"name": "<string>"
},
"senderProfile": {
"id": "<string>",
"name": "<string>",
"email": "<string>",
"integrationId": "<string>"
}
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}Create a marketing campaign template.
Author the email body as bodyDocument (the visual-builder JSON); the server renders bodyHtml from it. Sending bodyHtml is rejected with 400.
curl --request POST \
--url https://api.parallellabs.app/api/v0/marketing-campaign-templates \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"name": "<string>",
"description": "<string>",
"subject": "<string>",
"replyTo": "<string>",
"bodyDocument": "<string>",
"senderProfileId": "<string>",
"listId": "<string>",
"fieldMappings": {},
"scheduleSettings": "<string>",
"companyId": "<string>"
}
'import requests
url = "https://api.parallellabs.app/api/v0/marketing-campaign-templates"
payload = {
"name": "<string>",
"description": "<string>",
"subject": "<string>",
"replyTo": "<string>",
"bodyDocument": "<string>",
"senderProfileId": "<string>",
"listId": "<string>",
"fieldMappings": {},
"scheduleSettings": "<string>",
"companyId": "<string>"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: '<string>',
description: '<string>',
subject: '<string>',
replyTo: '<string>',
bodyDocument: '<string>',
senderProfileId: '<string>',
listId: '<string>',
fieldMappings: {},
scheduleSettings: '<string>',
companyId: '<string>'
})
};
fetch('https://api.parallellabs.app/api/v0/marketing-campaign-templates', 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.parallellabs.app/api/v0/marketing-campaign-templates",
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' => '<string>',
'description' => '<string>',
'subject' => '<string>',
'replyTo' => '<string>',
'bodyDocument' => '<string>',
'senderProfileId' => '<string>',
'listId' => '<string>',
'fieldMappings' => [
],
'scheduleSettings' => '<string>',
'companyId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"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"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.parallellabs.app/api/v0/marketing-campaign-templates"
payload := strings.NewReader("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"subject\": \"<string>\",\n \"replyTo\": \"<string>\",\n \"bodyDocument\": \"<string>\",\n \"senderProfileId\": \"<string>\",\n \"listId\": \"<string>\",\n \"fieldMappings\": {},\n \"scheduleSettings\": \"<string>\",\n \"companyId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
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.parallellabs.app/api/v0/marketing-campaign-templates")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"subject\": \"<string>\",\n \"replyTo\": \"<string>\",\n \"bodyDocument\": \"<string>\",\n \"senderProfileId\": \"<string>\",\n \"listId\": \"<string>\",\n \"fieldMappings\": {},\n \"scheduleSettings\": \"<string>\",\n \"companyId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.parallellabs.app/api/v0/marketing-campaign-templates")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"<string>\",\n \"description\": \"<string>\",\n \"subject\": \"<string>\",\n \"replyTo\": \"<string>\",\n \"bodyDocument\": \"<string>\",\n \"senderProfileId\": \"<string>\",\n \"listId\": \"<string>\",\n \"fieldMappings\": {},\n \"scheduleSettings\": \"<string>\",\n \"companyId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"companyId": "<string>",
"uid": "<string>",
"name": "<string>",
"description": "<string>",
"subject": "<string>",
"replyTo": "<string>",
"bodyHtml": "<string>",
"bodyDocument": "<string>",
"senderProfileId": "<string>",
"listId": "<string>",
"fieldMappings": {},
"scheduleSettings": "<string>",
"chatHistory": [
"<string>"
],
"created_date": "<string>",
"updated_date": "<string>",
"smartList": {
"id": "<string>",
"name": "<string>"
},
"senderProfile": {
"id": "<string>",
"name": "<string>",
"email": "<string>",
"integrationId": "<string>"
}
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}Authorizations
Company API key - scoped to a specific company. Generate from the Integrations page in your dashboard.
Body
Template fields. Only name is required up front.
Request body for creating a marketing campaign template. Only name is required; everything else can be filled in later via PATCH. The email body is authored as bodyDocument (visual-builder JSON); bodyHtml is rendered from it on the server and cannot be supplied directly.
Template name
Optional note describing when to use this template
Default email subject line applied to campaigns created from this template
Optional default Reply-To email address. Must be a valid email if provided.
Visual-builder JSON document for the email body. This is the single source of truth: the server renders bodyHtml from it (any bodyHtml you send is ignored). Envelope: {"subject": string, "subTitle": string, "content": }. Every node is {"type": string, "attributes": {MJML attributes as string values, e.g. "padding": "10px 25px 10px 25px", "background-color": "#ffffff", "color": "#333333", "font-size": "16px", "align": "center", "width": "600px", "href", "src"}, "data": {"value": {...}}, "children": [...]}; attribute values must be strings (numbers are dropped). Tree: page -> (wrapper | section) -> section -> column -> content blocks. Content block types: "text" (data.value.content = inline HTML string), "image" (attributes.src, alt, href, width), "button" (data.value.content = label, attributes.href), "divider", "spacer" (attributes.height), "social" (data.value.elements = [{src, href, content, target}]), "navbar" (data.value.links = [{href, content}]), "raw" (data.value.content = raw MJML/HTML), plus "hero", "group", "accordion", "carousel" and "table". Each also has an "advanced_" variant (e.g. "advanced_text"), which is what the builder palette inserts; advanced content blocks placed directly under the page or a wrapper are auto-wrapped in a section/column. Page node: {"type": "page", "attributes": {"background-color": "#efeeea", "width": "600px"}, "data": {"value": {"breakpoint": "480px", "font-family": string, "font-size": "14px", "line-height": "1.7", "font-weight": "400", "text-color": "#000000", "content-background-color": string (optional), "headStyles": [{"content": css, "inline": bool}], "fonts": [{"name", "href"}], "headAttributes": "", "responsive": true}}}. Always set "responsive": true — a missing flag renders a fixed-width layout. Merge tokens pass through rendering untouched: {{content}} inside a text block marks where content-engine output is inserted when content is published as a campaign from this template; {{variableName}} tokens (e.g. {{First Name}}) are replaced per recipient from the smart-list row; {{unsubscribe}} becomes the per-recipient unsubscribe link.
Default sender profile ID for campaigns created from this template
Default smart list ID whose rows become recipients
Field mapping for resolving recipient email from a list row, e.g. {"email": ["Email", "Work Email"]}
Optional sending-window settings copied onto campaigns created from this template
Company ID (falls back to the authenticated user's current company)
Response
The created template
A reusable marketing campaign template
Template ID
Company this template belongs to
User ID that created the template
Template name
Optional note describing when to use this template
Default email subject line
Default Reply-To email address
Email HTML rendered by the server from bodyDocument (read-only)
Visual-builder JSON document for the email body (source of truth)
Default sender profile ID
Default smart list ID
Field mapping for resolving recipient email from a list row
Optional sending-window settings
Saved AI chat history for this template (reserved for a future AI edit mode)
ISO timestamp when the template was created
ISO timestamp when the template was last updated
Summary of the default smart list
Show child attributes
Show child attributes
Summary of the default sender profile
Show child attributes
Show child attributes

