curl -X POST https://api.tracklysms.com/api/v2/send/bulk \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"to": "+14155551234",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}"
},
{
"to": "+14155559876",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}"
}
]
}'
import requests
response = requests.post(
"https://api.tracklysms.com/api/v2/send/bulk",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
json={
"messages": [
{
"to": "+14155551234",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}",
},
{
"to": "+14155559876",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}",
},
]
},
)
data = response.json()
print(f"Queued: {data['queued_count']}, Errors: {data['error_count']}")
const response = await fetch("https://api.tracklysms.com/api/v2/send/bulk", {
method: "POST",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
messages: [
{
to: "+14155551234",
list_number: "+18005551000",
body: "Your promo code is SAVE20. Ref: {{messageId}}",
},
{
to: "+14155559876",
list_number: "+18005551000",
body: "Your promo code is SAVE20. Ref: {{messageId}}",
},
],
}),
});
const data = await response.json();
console.log(`Queued: ${data.queued_count}, Errors: ${data.error_count}`);
{
"queued_count": 2,
"error_count": 0,
"errors": []
}
{
"queued_count": 1,
"error_count": 1,
"errors": [
{
"index": 1,
"to": "not-a-number",
"code": "invalid_phone",
"error": "Invalid recipient phone number format"
}
]
}
{
"error": "messages array is required",
"code": "missing_messages"
}
{
"error": "Maximum 1000 messages per request",
"code": "too_many_messages"
}
Messages (v2)
Send Bulk Messages
Send multiple SMS messages in a single bulk request.
POST
/
v2
/
send
/
bulk
curl -X POST https://api.tracklysms.com/api/v2/send/bulk \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"to": "+14155551234",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}"
},
{
"to": "+14155559876",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}"
}
]
}'
import requests
response = requests.post(
"https://api.tracklysms.com/api/v2/send/bulk",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
json={
"messages": [
{
"to": "+14155551234",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}",
},
{
"to": "+14155559876",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}",
},
]
},
)
data = response.json()
print(f"Queued: {data['queued_count']}, Errors: {data['error_count']}")
const response = await fetch("https://api.tracklysms.com/api/v2/send/bulk", {
method: "POST",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
messages: [
{
to: "+14155551234",
list_number: "+18005551000",
body: "Your promo code is SAVE20. Ref: {{messageId}}",
},
{
to: "+14155559876",
list_number: "+18005551000",
body: "Your promo code is SAVE20. Ref: {{messageId}}",
},
],
}),
});
const data = await response.json();
console.log(`Queued: ${data.queued_count}, Errors: ${data.error_count}`);
{
"queued_count": 2,
"error_count": 0,
"errors": []
}
{
"queued_count": 1,
"error_count": 1,
"errors": [
{
"index": 1,
"to": "not-a-number",
"code": "invalid_phone",
"error": "Invalid recipient phone number format"
}
]
}
{
"error": "messages array is required",
"code": "missing_messages"
}
{
"error": "Maximum 1000 messages per request",
"code": "too_many_messages"
}
Send up to 1,000 SMS messages in a single API call. Each message in the batch is validated independently — successfully validated messages are queued even if others in the batch fail validation.
Individual messages that fail are reported per-message (below) and the request still returns
Body Parameters
array
required
Array of message objects (maximum 1,000 per request). Each object supports the following fields:
Show Message object fields
Show Message object fields
string
required
Recipient phone number in E.164 format (e.g.
+14155551234).string
required
Sending list phone number in E.164 format. Must belong to your account.
string
required
Message body text. Use the
{{messageId}} placeholder to insert the unique message ID into the body at send time.boolean
default:"false"
Intended to auto-shorten URLs in the message body for click tracking, but this is not currently active — raw URLs are sent unshortened. For click-tracked links, create the link via the Shorten Link endpoint and put the returned short URL in your body.
object
Optional per-contact data for macro substitution. Supports
first_name (string), last_name (string), custom_fields (object), and timezone (string).object
Optional key-value metadata dictionary. Currently accepted but not stored or returned — the endpoint reads this field and then discards it, so it cannot yet be used for correlation.
Response Fields
integer
Number of messages successfully queued for delivery.
integer
Number of messages that failed validation or queueing.
array
Array of error objects for messages that failed validation or queueing. Each object contains:
Show Error object fields
Show Error object fields
integer
Zero-based index of the failed message in the original
messages array.string
The recipient phone number from the failed message, if provided.
string
Machine-readable error code identifying the failure.
string
Human-readable error description.
string
Present only when
code is kafka_producer_failure. Constant value KAFKA_PRODUCER_FAILURE, indicating the message passed validation but could not be published to the delivery queue.string
Present only when
code is kafka_producer_failure. The message ID that was assigned before the publish failure.Examples
curl -X POST https://api.tracklysms.com/api/v2/send/bulk \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"to": "+14155551234",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}"
},
{
"to": "+14155559876",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}"
}
]
}'
import requests
response = requests.post(
"https://api.tracklysms.com/api/v2/send/bulk",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
json={
"messages": [
{
"to": "+14155551234",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}",
},
{
"to": "+14155559876",
"list_number": "+18005551000",
"body": "Your promo code is SAVE20. Ref: {{messageId}}",
},
]
},
)
data = response.json()
print(f"Queued: {data['queued_count']}, Errors: {data['error_count']}")
const response = await fetch("https://api.tracklysms.com/api/v2/send/bulk", {
method: "POST",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
messages: [
{
to: "+14155551234",
list_number: "+18005551000",
body: "Your promo code is SAVE20. Ref: {{messageId}}",
},
{
to: "+14155559876",
list_number: "+18005551000",
body: "Your promo code is SAVE20. Ref: {{messageId}}",
},
],
}),
});
const data = await response.json();
console.log(`Queued: ${data.queued_count}, Errors: ${data.error_count}`);
{
"queued_count": 2,
"error_count": 0,
"errors": []
}
{
"queued_count": 1,
"error_count": 1,
"errors": [
{
"index": 1,
"to": "not-a-number",
"code": "invalid_phone",
"error": "Invalid recipient phone number format"
}
]
}
{
"error": "messages array is required",
"code": "missing_messages"
}
{
"error": "Maximum 1000 messages per request",
"code": "too_many_messages"
}
Error Codes
Request-Level Errors
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | missing_messages | The messages array is required but was not provided. |
| 413 | too_many_messages | The messages array exceeds the 1,000 message limit. |
201. The request returns 502 only when every message fails to queue for delivery. Authenticated requests can also fail with 401 invalid_credentials, 403 account_suspended, or 429 rate_limited — see Error Codes.
Per-Message Errors
Returned inside each failed entry of the response’serrors[] array (as code), with the request status 201.
| Code | Description |
|---|---|
missing_to | The to field is required but was not provided. |
missing_list_number | The list_number field is required but was not provided. |
missing_body | The body field is required but was not provided. |
invalid_phone | The to field is not a valid E.164 phone number. |
invalid_list_number | The list_number field is not a valid E.164 phone number. |
list_not_found | The sending list was not found or does not belong to your account. |
webhook_not_configured | Webhook verification required before sending (BYOC lists). |
pending_confirmation | The recipient has not confirmed their double opt-in yet, so they cannot be messaged. |
doi_expired | The recipient never confirmed their double opt-in and the confirmation window has expired. |
warmup_limit | The recipient is not yet eligible to be messaged under the list’s warm-up schedule. |
kafka_producer_failure | The message could not be queued for delivery (transient); retry the failed records. |
Next Steps
Campaign Execution
How messages flow from queue to delivery
Create Contact
Add contacts before sending
⌘I