curl -X PUT "https://api.tracklysms.com/api/v2/creatives/101" \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204"
}
]
}'
import requests
response = requests.put(
"https://api.tracklysms.com/api/v2/creatives/101",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json"
},
json={
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204"
}
]
}
)
data = response.json()
print(f"Updated: {data['name']}")
print(f"New segment count: {data['segment_count']}")
const response = await fetch(
"https://api.tracklysms.com/api/v2/creatives/101",
{
method: "PUT",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Summer Sale Promo v2",
message:
"Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
offer_links: [
{
key: "link1",
offer_id: "665f1a2b3c4d5e6f70819201",
url_params: { sub1: "sms" },
},
{
key: "link2",
offer_id: "665f1a2b3c4d5e6f70819204",
},
],
}),
}
);
const data = await response.json();
console.log(`Updated: ${data.name}`);
{
"id": 101,
"slug": "cre_summer-sale-promo-v2_a1b2c3",
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"custom_url": null,
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204",
"custom_url": null,
"url_params": {}
}
],
"offers": ["665f1a2b3c4d5e6f70819201", "665f1a2b3c4d5e6f70819204"],
"status": "active",
"creative_type": "automated",
"character_count": 151,
"segment_count": 1,
"created_at": "2025-06-10T12:00:00",
"list_control_mode": "all",
"list_control_ids": []
}
{
"error": "Missing offer_links for placeholders: link2",
"code": "missing_offer_links_for_placeholders"
}
{
"error": "Creative not found",
"code": "not_found"
}
{
"error": "Invalid credentials",
"code": "invalid_credentials"
}
Creatives (v2)
Update a Creative
Update an existing creative. Only provided fields are modified.
PUT
/
v2
/
creatives
/
{id}
curl -X PUT "https://api.tracklysms.com/api/v2/creatives/101" \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204"
}
]
}'
import requests
response = requests.put(
"https://api.tracklysms.com/api/v2/creatives/101",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json"
},
json={
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204"
}
]
}
)
data = response.json()
print(f"Updated: {data['name']}")
print(f"New segment count: {data['segment_count']}")
const response = await fetch(
"https://api.tracklysms.com/api/v2/creatives/101",
{
method: "PUT",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Summer Sale Promo v2",
message:
"Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
offer_links: [
{
key: "link1",
offer_id: "665f1a2b3c4d5e6f70819201",
url_params: { sub1: "sms" },
},
{
key: "link2",
offer_id: "665f1a2b3c4d5e6f70819204",
},
],
}),
}
);
const data = await response.json();
console.log(`Updated: ${data.name}`);
{
"id": 101,
"slug": "cre_summer-sale-promo-v2_a1b2c3",
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"custom_url": null,
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204",
"custom_url": null,
"url_params": {}
}
],
"offers": ["665f1a2b3c4d5e6f70819201", "665f1a2b3c4d5e6f70819204"],
"status": "active",
"creative_type": "automated",
"character_count": 151,
"segment_count": 1,
"created_at": "2025-06-10T12:00:00",
"list_control_mode": "all",
"list_control_ids": []
}
{
"error": "Missing offer_links for placeholders: link2",
"code": "missing_offer_links_for_placeholders"
}
{
"error": "Creative not found",
"code": "not_found"
}
{
"error": "Invalid credentials",
"code": "invalid_credentials"
}
Updates an existing creative in your account. Only supplied fields change; omitted fields
remain unchanged. This also applies to
HTTP
message, offer_links, and creative_type:
validation combines your supplied values with the asset’s stored values.
You can change message text alone when its placeholders still match the stored mappings,
or replace offer_links alone when they cover the stored message’s placeholders. Send both
when changing the placeholders and their mappings together. Changing either text or links
recalculates character_count and segment_count.
Plain text without links is valid for one_time assets. Changing such an asset to automated
or welcome requires adding a message with at least one mapped {{linkN}} placeholder in
the same request. Editing an asset saved during blast creation does not change that schedule.
Path Parameters
integer
required
The unique identifier of the creative to update.
Body Parameters
All body parameters are optional. Include only the fields you want to change.string
Updated display name. Maximum 255 characters.
string
Updated message text containing non-whitespace text, at most 1600 characters. A supplied message must pass content checks. Plain text is valid for
one_time; other types require at least one {{linkN}} placeholder. Validated against stored offer_links and creative_type unless you replace those fields in the same request.array
Replaces the entire mappings array; omitting it retains stored mappings. Every
{{linkN}} placeholder in the effective message must have an entry. Use [] to clear mappings when the effective message has no placeholders and its type is one_time.Show Offer link properties
Show Offer link properties
string
required
A unique, lowercase
linkN key matching the message placeholder (e.g. link1).string
Must be a 24-character hexadecimal ID, and the offer must exist and belong to your account. Exactly one of
offer_id or custom_url is required for each entry.string
A custom destination URL for this link, used instead of an offer. Must be an HTTPS URL, and must not point to a private or non-public address. Exactly one of
offer_id or custom_url is required for each entry.string
Deprecated and ignored. Use
custom_url for a custom destination.object
Optional additional query parameters.
string
Updated status. Accepted values:
active, paused, archived. Note that unlike creation, you can set the status to archived during an update.string
Accepted values:
automated, one_time, welcome. Changing this field validates the effective message and mappings against the new type. Only one_time supports messages without link placeholders.string
List targeting control mode. Accepted values:
all, include, exclude.array
List IDs as JSON integers to include or exclude when
list_control_mode is include or exclude. Boolean values are rejected.Response Fields
The response is the full updated creative object (flat, not wrapped).Show Creative object properties
Show Creative object properties
integer
Unique identifier for the creative.
string
Auto-generated unique identifier (e.g.
cre_summer-sale-promo_a1b2c3).string
Display name of the creative.
string
Message text, optionally containing
{{linkN}} placeholders for a one_time creative.array
Array of offer link objects.
array
Array of offer ID strings referenced by this creative.
string
Current status of the creative.
string
The type of creative.
integer
Estimated character count (recalculated if message or offer_links changed).
integer
Estimated SMS segment count (recalculated if message or offer_links changed).
datetime
ISO 8601 timestamp of when the creative was originally created.
string
List targeting control mode. One of:
all (default), include, exclude.array
List IDs used when
list_control_mode is include or exclude.Examples
If creative101 is a one_time asset, this updates its text and clears its links while
preserving its name, status, and list controls:
curl -X PUT "https://api.tracklysms.com/api/v2/creatives/101" \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"message":"Our sale ends tonight! Reply STOP to opt out.","offer_links":[]}'
200 returns the full updated creative directly, with its ID in id.
curl -X PUT "https://api.tracklysms.com/api/v2/creatives/101" \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204"
}
]
}'
import requests
response = requests.put(
"https://api.tracklysms.com/api/v2/creatives/101",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json"
},
json={
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204"
}
]
}
)
data = response.json()
print(f"Updated: {data['name']}")
print(f"New segment count: {data['segment_count']}")
const response = await fetch(
"https://api.tracklysms.com/api/v2/creatives/101",
{
method: "PUT",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Summer Sale Promo v2",
message:
"Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
offer_links: [
{
key: "link1",
offer_id: "665f1a2b3c4d5e6f70819201",
url_params: { sub1: "sms" },
},
{
key: "link2",
offer_id: "665f1a2b3c4d5e6f70819204",
},
],
}),
}
);
const data = await response.json();
console.log(`Updated: ${data.name}`);
{
"id": 101,
"slug": "cre_summer-sale-promo-v2_a1b2c3",
"name": "Summer Sale Promo v2",
"message": "Exclusive summer deals! Shop now: {{link1}} Browse more: {{link2}} Reply STOP to opt out.",
"offer_links": [
{
"key": "link1",
"offer_id": "665f1a2b3c4d5e6f70819201",
"custom_url": null,
"url_params": {"sub1": "sms"}
},
{
"key": "link2",
"offer_id": "665f1a2b3c4d5e6f70819204",
"custom_url": null,
"url_params": {}
}
],
"offers": ["665f1a2b3c4d5e6f70819201", "665f1a2b3c4d5e6f70819204"],
"status": "active",
"creative_type": "automated",
"character_count": 151,
"segment_count": 1,
"created_at": "2025-06-10T12:00:00",
"list_control_mode": "all",
"list_control_ids": []
}
{
"error": "Missing offer_links for placeholders: link2",
"code": "missing_offer_links_for_placeholders"
}
{
"error": "Creative not found",
"code": "not_found"
}
{
"error": "Invalid credentials",
"code": "invalid_credentials"
}
Error Codes
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | name_too_long | The name exceeds 255 characters. |
| 400 | missing_message | message must be a string containing non-whitespace text. |
| 400 | message_too_long | The message exceeds 1600 characters. |
| 400 | blocked_content | The supplied message matches a prohibited-content rule. Metadata/status-only updates do not recheck stored message content. |
| 400 | invalid_status | The provided status is not active, paused, or archived. |
| 400 | invalid_creative_type | The provided creative_type is not automated, one_time, or welcome. |
| 400 | invalid_list_control_mode | The provided list_control_mode is not all, include, or exclude. |
| 400 | invalid_list_control_ids | list_control_ids must be an array of JSON integers; boolean values are rejected. |
| 400 | missing_link_placeholder | The effective message must contain at least one {{linkN}} placeholder when the effective type is automated or welcome. |
| 400 | missing_offer_links_for_placeholders | One or more {{linkN}} placeholders in the message do not have a matching entry in offer_links. |
| 400 | missing_offer_link_key | An entry in offer_links is missing the required key field. |
| 400 | invalid_offer_link | An entry in offer_links must include exactly one of offer_id or custom_url — an offer or a custom URL is required. |
| 400 | invalid_offer_link_offer_id | An offer_id in offer_links is not a valid 24-character hexadecimal ID. |
| 400 | invalid_offer_link_url | A custom_url in offer_links is not a valid HTTPS URL, or it points to a private or non-public address. |
| 400 | invalid_offer_links | Mappings must be objects with unique lowercase linkN keys and object url_params. |
| 400 | invalid_offer_link_source | An offer mapping specifies an unsupported offer_source. |
| 401 | invalid_credentials | API key is missing or invalid. |
| 403 | account_suspended | Your account is suspended. Resolve outstanding billing or contact support. |
| 404 | not_found | No creative exists with the given ID. |
| 400 | offer_not_found | An offer referenced in offer_links does not exist or does not belong to your account. |
Next Steps
Creative Templates
Template best practices
Create Schedule
Use creatives in campaigns