curl --request POST \
--url https://core-api.getaptly.com/api/templates \
--header 'Content-Type: application/json' \
--header 'x-token: <api-key>' \
--data '
{
"companyId": "{{companyId}}",
"userId": "{{userId}}",
"name": "Move-in welcome",
"description": "Sent to new residents on move-in day",
"templateType": "email",
"subject": "Welcome to your new home",
"content": "<p>Welcome!</p>",
"htmlBuilder": false
}
'import requests
url = "https://core-api.getaptly.com/api/templates"
payload = {
"companyId": "{{companyId}}",
"userId": "{{userId}}",
"name": "Move-in welcome",
"description": "Sent to new residents on move-in day",
"templateType": "email",
"subject": "Welcome to your new home",
"content": "<p>Welcome!</p>",
"htmlBuilder": False
}
headers = {
"x-token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-token': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
companyId: '{{companyId}}',
userId: '{{userId}}',
name: 'Move-in welcome',
description: 'Sent to new residents on move-in day',
templateType: 'email',
subject: 'Welcome to your new home',
content: '<p>Welcome!</p>',
htmlBuilder: false
})
};
fetch('https://core-api.getaptly.com/api/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://core-api.getaptly.com/api/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([
'companyId' => '{{companyId}}',
'userId' => '{{userId}}',
'name' => 'Move-in welcome',
'description' => 'Sent to new residents on move-in day',
'templateType' => 'email',
'subject' => 'Welcome to your new home',
'content' => '<p>Welcome!</p>',
'htmlBuilder' => false
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-token: <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://core-api.getaptly.com/api/templates"
payload := strings.NewReader("{\n \"companyId\": \"{{companyId}}\",\n \"userId\": \"{{userId}}\",\n \"name\": \"Move-in welcome\",\n \"description\": \"Sent to new residents on move-in day\",\n \"templateType\": \"email\",\n \"subject\": \"Welcome to your new home\",\n \"content\": \"<p>Welcome!</p>\",\n \"htmlBuilder\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-token", "<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://core-api.getaptly.com/api/templates")
.header("x-token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"companyId\": \"{{companyId}}\",\n \"userId\": \"{{userId}}\",\n \"name\": \"Move-in welcome\",\n \"description\": \"Sent to new residents on move-in day\",\n \"templateType\": \"email\",\n \"subject\": \"Welcome to your new home\",\n \"content\": \"<p>Welcome!</p>\",\n \"htmlBuilder\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://core-api.getaptly.com/api/templates")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"companyId\": \"{{companyId}}\",\n \"userId\": \"{{userId}}\",\n \"name\": \"Move-in welcome\",\n \"description\": \"Sent to new residents on move-in day\",\n \"templateType\": \"email\",\n \"subject\": \"Welcome to your new home\",\n \"content\": \"<p>Welcome!</p>\",\n \"htmlBuilder\": false\n}"
response = http.request(request)
puts response.read_body{
"data": {
"_id": "<string>",
"companyId": "<string>",
"name": "<string>",
"templateType": "sms",
"archived": true,
"createdAt": "2023-11-07T05:31:56Z",
"createdBy": "<string>",
"updatedAt": "2023-11-07T05:31:56Z"
}
}Create a template
Creates a communication template.
The acting user — the delegate token’s own user, or the body userId when
authenticating with an API key or partner token — must belong to the company and
hold the setup or setup_communication_templates permission.
Only plain templates can be created here — htmlBuilder must be false or
omitted.
attachmentIds are resolved against the company’s uploaded files, stored on the
template as full attachment entries, and tied to it so the detached-upload cleanup
leaves them alone — ids that no longer resolve are dropped. A pdf template’s
subject becomes a file name, so characters that are illegal in one are stripped.
Merge fields
subject and content may contain merge placeholders, which are replaced with
the recipient’s, board card’s or company’s own values when the template is sent.
Fetch the available fields from
GET /api/board/{boardId}/merge-fields for the board the template is used with.
Every item in that response is { label, value } — label is what to show a
human, value is what goes inside the braces:
{{firstname}}
{{Locations["Rent"]}}
{{customFields.7Lq2.LoyaltyTier}}
A placeholder may carry a fallback, used when the field is empty or unknown:
{{firstname || fallback: there}} -> "there" when the contact has no first name
{{Locations["Rent"] || fallback: TBD}} -> "TBD" when the card has no rent
{{firstname || fallback: }} -> renders nothing at all
Everything after fallback: up to the closing braces is the fallback text, and an
empty one (or _blank) renders nothing.
Placeholders are shape-checked on write — an unclosed {{, an empty field
name, or a malformed bracket reference such as Locations["Rent] is rejected with
400. Whether the field exists is not checked: an unknown field is normal (boards
differ), and simply renders as its fallback at merge time.
curl --request POST \
--url https://core-api.getaptly.com/api/templates \
--header 'Content-Type: application/json' \
--header 'x-token: <api-key>' \
--data '
{
"companyId": "{{companyId}}",
"userId": "{{userId}}",
"name": "Move-in welcome",
"description": "Sent to new residents on move-in day",
"templateType": "email",
"subject": "Welcome to your new home",
"content": "<p>Welcome!</p>",
"htmlBuilder": false
}
'import requests
url = "https://core-api.getaptly.com/api/templates"
payload = {
"companyId": "{{companyId}}",
"userId": "{{userId}}",
"name": "Move-in welcome",
"description": "Sent to new residents on move-in day",
"templateType": "email",
"subject": "Welcome to your new home",
"content": "<p>Welcome!</p>",
"htmlBuilder": False
}
headers = {
"x-token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-token': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
companyId: '{{companyId}}',
userId: '{{userId}}',
name: 'Move-in welcome',
description: 'Sent to new residents on move-in day',
templateType: 'email',
subject: 'Welcome to your new home',
content: '<p>Welcome!</p>',
htmlBuilder: false
})
};
fetch('https://core-api.getaptly.com/api/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://core-api.getaptly.com/api/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([
'companyId' => '{{companyId}}',
'userId' => '{{userId}}',
'name' => 'Move-in welcome',
'description' => 'Sent to new residents on move-in day',
'templateType' => 'email',
'subject' => 'Welcome to your new home',
'content' => '<p>Welcome!</p>',
'htmlBuilder' => false
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-token: <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://core-api.getaptly.com/api/templates"
payload := strings.NewReader("{\n \"companyId\": \"{{companyId}}\",\n \"userId\": \"{{userId}}\",\n \"name\": \"Move-in welcome\",\n \"description\": \"Sent to new residents on move-in day\",\n \"templateType\": \"email\",\n \"subject\": \"Welcome to your new home\",\n \"content\": \"<p>Welcome!</p>\",\n \"htmlBuilder\": false\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-token", "<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://core-api.getaptly.com/api/templates")
.header("x-token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"companyId\": \"{{companyId}}\",\n \"userId\": \"{{userId}}\",\n \"name\": \"Move-in welcome\",\n \"description\": \"Sent to new residents on move-in day\",\n \"templateType\": \"email\",\n \"subject\": \"Welcome to your new home\",\n \"content\": \"<p>Welcome!</p>\",\n \"htmlBuilder\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://core-api.getaptly.com/api/templates")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"companyId\": \"{{companyId}}\",\n \"userId\": \"{{userId}}\",\n \"name\": \"Move-in welcome\",\n \"description\": \"Sent to new residents on move-in day\",\n \"templateType\": \"email\",\n \"subject\": \"Welcome to your new home\",\n \"content\": \"<p>Welcome!</p>\",\n \"htmlBuilder\": false\n}"
response = http.request(request)
puts response.read_body{
"data": {
"_id": "<string>",
"companyId": "<string>",
"name": "<string>",
"templateType": "sms",
"archived": true,
"createdAt": "2023-11-07T05:31:56Z",
"createdBy": "<string>",
"updatedAt": "2023-11-07T05:31:56Z"
}
}Authorizations
Body
Template kind. blockDocument is not accepted — it is an internal type for document blocks, not a user-facing template.
sms, email, form, eSignature, pdf Email subject, or the file name for a pdf template — in which case filename-illegal characters are stripped. Merge placeholders work here too, and are shape-checked the same way.
Template body. Merge placeholders take the form {{value}} or {{value || fallback: text}}, where value is an item's value from GET /api/board/{boardId}/merge-fields — for example {{firstname}}, {{Locations["Rent"] || fallback: TBD}}. They are checked for shape — an unclosed {{, an empty field name or a malformed bracket reference is rejected. Whether each field exists is not checked: an unknown one renders as its fallback at merge time.
Required with a partner token; resolved from an API key or delegate token.
The acting user. Required with an API key or partner token; a delegate token supplies its own.
Must be false or omitted. HTML-builder templates keep their markup in a builder-owned builderData format and can only be authored in the app.
false Builder document, when one already exists.
Library folder the template belongs to — must be an existing, non-archived folder in the company. Governs who may edit the template.
Board the template is scoped to. Must be an existing, non-archived board in the company.
Uploaded file ids to attach.
Response
Template created.
Show child attributes
Show child attributes