{
"success_count": 2,
"error_count": 0,
"duplicates_skipped": 0,
"errors": []
}
{
"success_count": 1,
"error_count": 0,
"duplicates_skipped": 1,
"errors": []
}
{
"error": "records array is required",
"code": "missing_records"
}
{
"error": "Maximum 1000 records per request",
"code": "too_many_records"
}
Data Import (v2)
Import Click History
Import historical click records for previously sent messages.
POST
/
v2
/
history
/
clicks
{
"success_count": 2,
"error_count": 0,
"duplicates_skipped": 0,
"errors": []
}
{
"success_count": 1,
"error_count": 0,
"duplicates_skipped": 1,
"errors": []
}
{
"error": "records array is required",
"code": "missing_records"
}
{
"error": "Maximum 1000 records per request",
"code": "too_many_records"
}
Bulk import up to 1,000 historical click records per request. Each click is associated with an existing message and offer. Only one click per
Authenticated requests can also fail with Per-record errors (
message_id is stored, so duplicate entries are automatically skipped.
Importing clicks also updates the contact’s per-list stats: click_count and last_clicked_at.
Authentication
string
required
Your Trackly SMS API key, formatted as
trk_ followed by 32 lowercase alphanumeric characters. It can also be supplied as an Authorization: Bearer header instead of X-Api-Key.Body Parameters
array
required
An array of click records to import. Maximum of 1,000 records per request. Each record accepts the following fields:
Show Record fields
Show Record fields
string
required
The message ID this click is associated with. The message must already exist in the system.
string
required
The offer ID that was clicked. The offer must already exist in the system.
string
required
The contact’s phone number in E.164 format.
datetime
The original send timestamp of the associated message in ISO 8601 format.
datetime
required
The click timestamp in ISO 8601 format.
Response Fields
integer
Number of click records successfully imported.
integer
Number of records that failed validation or processing.
integer
Number of records skipped because a click already exists for that
message_id.array
Array of error objects (maximum 100 returned). Each object contains:
index(integer) — Position of the failed record in the input array.message_id(string) — Themessage_idof the failed record.code(string) — Machine-readable error code.error(string) — Human-readable error description.
cURL
curl -X POST https://api.tracklysms.com/api/v2/history/clicks \
-H "X-Api-Key: trk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"records": [
{
"message_id": "a1b2c3d4",
"offer_id": "offer_123",
"phone_number": "+12025559876",
"send_timestamp": "2025-11-15T14:30:00Z",
"timestamp": "2025-11-15T14:35:22Z"
},
{
"message_id": "e5f6g7h8",
"offer_id": "offer_456",
"phone_number": "+13105558888",
"timestamp": "2025-11-15T15:10:45Z"
}
]
}'
message_id is an opaque identifier — do not assume a fixed length or format. Store them as variable-length strings.Python
import requests
response = requests.post(
"https://api.tracklysms.com/api/v2/history/clicks",
headers={
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
json={
"records": [
{
"message_id": "a1b2c3d4",
"offer_id": "offer_123",
"phone_number": "+12025559876",
"send_timestamp": "2025-11-15T14:30:00Z",
"timestamp": "2025-11-15T14:35:22Z",
},
{
"message_id": "e5f6g7h8",
"offer_id": "offer_456",
"phone_number": "+13105558888",
"timestamp": "2025-11-15T15:10:45Z",
},
],
},
)
print(response.json())
Node.js
const response = await fetch("https://api.tracklysms.com/api/v2/history/clicks", {
method: "POST",
headers: {
"X-Api-Key": "trk_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
records: [
{
message_id: "a1b2c3d4",
offer_id: "offer_123",
phone_number: "+12025559876",
send_timestamp: "2025-11-15T14:30:00Z",
timestamp: "2025-11-15T14:35:22Z",
},
{
message_id: "e5f6g7h8",
offer_id: "offer_456",
phone_number: "+13105558888",
timestamp: "2025-11-15T15:10:45Z",
},
],
}),
});
const data = await response.json();
console.log(data);
{
"success_count": 2,
"error_count": 0,
"duplicates_skipped": 0,
"errors": []
}
{
"success_count": 1,
"error_count": 0,
"duplicates_skipped": 1,
"errors": []
}
{
"error": "records array is required",
"code": "missing_records"
}
{
"error": "Maximum 1000 records per request",
"code": "too_many_records"
}
Error Codes
Only two conditions reject the whole request. Every other error is reported per record: the request still returns201, and each failed record appears in the response’s errors[] array with a code (see the Partial Success example above).
Request errors (HTTP status)
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | missing_records | The records field is required and must be a non-empty array. |
| 413 | too_many_records | Exceeded the maximum of 1,000 records per request. |
401 invalid_credentials or 403 account_suspended — see Error Codes.
Per-record errors (errors[].code, returned with HTTP 201)
| Error Code | Description |
|---|---|
missing_message_id | A record is missing the message_id field. |
missing_offer_id | A record is missing the offer_id field. |
missing_phone_number | A record is missing the phone_number field. |
missing_timestamp | A record is missing the timestamp field. |
message_not_found | No message exists with the given message_id. |
offer_not_found | No offer exists with the given offer_id. |
invalid_timestamp | The timestamp is not a valid ISO 8601 datetime. |
save_error | An unexpected error occurred while saving the record. |
Next Steps
Reporting Overview
Analyze click data
Bulk Create Contacts
Import contacts alongside history