curl -G https://api.tracklysms.com/api/v2/messages \
-H "X-Api-Key: trk_your_api_key" \
--data-urlencode "updated_since=2026-07-26T00:00:00Z" \
--data-urlencode "limit=200"
import requests
resp = requests.get(
"https://api.tracklysms.com/api/v2/messages",
headers={"X-Api-Key": "trk_your_api_key"},
params={"status": "delivered", "limit": 200},
)
data = resp.json()
for msg in data["messages"]:
print(msg["id"], msg["status"])
print("next:", data["pagination"]["next_cursor"])
{
"messages": [
{
"id": "a1b2c3d4",
"from_number": "+18005551234",
"to_number": "+14155551234",
"body": "Hi",
"status": "delivered",
"status_detail": null,
"segment_count": 1,
"encoding": "GSM-7",
"sending_list_id": 88,
"timestamp": "2026-07-20T14:03:11.000000Z",
"updated_at": "2026-07-20T14:05:00.000000Z",
"dlr_status": "delivered",
"is_terminal": true,
"is_delivered": true,
"error_code": null,
"error_description": null,
"provider_message_id": "O3iFf1Eu",
"reply_id": null,
"replied_at": null,
"reply_text": null,
"reply_kind": null,
"api_key_id": "66f1c2a9b8e4d3f0a1b2c3d4",
"cost_state": "billed",
"external_ids": { "external_location_id": "loc_west" }
}
],
"pagination": { "limit": 200, "has_more": true, "next_cursor": "MjAyNi0wNy..." }
}
Messages (v2)
List Messages
List and filter sent messages, with cursor pagination and an updated_since reconciliation watermark.
GET
/
v2
/
messages
curl -G https://api.tracklysms.com/api/v2/messages \
-H "X-Api-Key: trk_your_api_key" \
--data-urlencode "updated_since=2026-07-26T00:00:00Z" \
--data-urlencode "limit=200"
import requests
resp = requests.get(
"https://api.tracklysms.com/api/v2/messages",
headers={"X-Api-Key": "trk_your_api_key"},
params={"status": "delivered", "limit": 200},
)
data = resp.json()
for msg in data["messages"]:
print(msg["id"], msg["status"])
print("next:", data["pagination"]["next_cursor"])
{
"messages": [
{
"id": "a1b2c3d4",
"from_number": "+18005551234",
"to_number": "+14155551234",
"body": "Hi",
"status": "delivered",
"status_detail": null,
"segment_count": 1,
"encoding": "GSM-7",
"sending_list_id": 88,
"timestamp": "2026-07-20T14:03:11.000000Z",
"updated_at": "2026-07-20T14:05:00.000000Z",
"dlr_status": "delivered",
"is_terminal": true,
"is_delivered": true,
"error_code": null,
"error_description": null,
"provider_message_id": "O3iFf1Eu",
"reply_id": null,
"replied_at": null,
"reply_text": null,
"reply_kind": null,
"api_key_id": "66f1c2a9b8e4d3f0a1b2c3d4",
"cost_state": "billed",
"external_ids": { "external_location_id": "loc_west" }
}
],
"pagination": { "limit": 200, "has_more": true, "next_cursor": "MjAyNi0wNy..." }
}
Lists messages for the account the key belongs to. By default rows are returned newest-first for browsing. Pass
Also
updated_since to turn the endpoint into an incremental reconciliation feed.
Query Parameters
integer
Filter to one sending list by its ID.
string
Filter by sending-list phone number (E.164). Ignored when
sending_list_id is set; an unmatched number returns zero rows.string
Filter by recipient phone number (E.164).
string
One of
queued, sent, failed, dropped, orphaned, delivered, undelivered.string
Lower bound on
timestamp (ISO-8601, timezone-aware — include Z or an offset). A naive datetime is rejected.string
Upper bound on
timestamp (ISO-8601, timezone-aware).string
Reconciliation watermark (ISO-8601, timezone-aware). When set, returns rows with
updated_at greater than this value, sorted oldest-change-first and keyset-paginated on (updated_at, id). See Reconciliation.string
Scope results to an owned account (yourself or a child) carrying this external location ID. An unowned location returns an empty result, never another tenant’s data.
string
Opaque pagination cursor from a previous response’s
next_cursor.integer
default:"50"
Page size, max 200.
string
me to return only messages sent with the calling API key. Any other value is rejected with 400 invalid_param.Response Fields
array
Array of message rows.
Show message row
Show message row
string
Opaque message ID (variable length).
string
Sending list number (E.164).
string
Recipient (E.164).
string
Message body.
string
Pipeline status.
string
Extra status context, if any.
integer
SMS segments.
string
GSM-7 or UCS-2.integer
The list this was sent from.
string
Send time (ISO-8601, UTC).
string
Last-change time (ISO-8601, UTC); the reconciliation watermark field.
string
Normalized delivery-receipt status.
boolean
Whether the delivery status is final.
boolean
Whether delivery is confirmed.
string
Carrier/pipeline error code, if failed.
string
Human-readable error, if failed.
string
Carrier-route message reference. Present only for providers whose IDs are globally unique;
null otherwise.string
ID of the linked inbound reply, if any.
string
When the recipient replied, if they did.
string
Text of the most recent linked inbound reply, capped at 1600 characters.
null when there is no reply and also when the reply carried no text (a media-only or reaction reply), so use reply_id to tell “no reply” from “textless reply”. Treat it as untrusted user input.string
How the most recent reply was classified:
human, reaction, stop, start, help, or keyword; null when there is no reply or the reply predates classification. This is a hint, not the opt-out signal: check preflight not_suppressed before sending again. A reply does not change the message’s updated_at, so poll by ID or list by to to pick up new replies.string
ID of the API key that sent the message;
null for dashboard, scheduled, journey, or pre-attribution sends.string
estimated, settled, billed, or refunded.object
External IDs of the owning account (e.g.
external_location_id).object
limit, has_more (boolean), and next_cursor (opaque, null on the last page).Examples
curl -G https://api.tracklysms.com/api/v2/messages \
-H "X-Api-Key: trk_your_api_key" \
--data-urlencode "updated_since=2026-07-26T00:00:00Z" \
--data-urlencode "limit=200"
import requests
resp = requests.get(
"https://api.tracklysms.com/api/v2/messages",
headers={"X-Api-Key": "trk_your_api_key"},
params={"status": "delivered", "limit": 200},
)
data = resp.json()
for msg in data["messages"]:
print(msg["id"], msg["status"])
print("next:", data["pagination"]["next_cursor"])
{
"messages": [
{
"id": "a1b2c3d4",
"from_number": "+18005551234",
"to_number": "+14155551234",
"body": "Hi",
"status": "delivered",
"status_detail": null,
"segment_count": 1,
"encoding": "GSM-7",
"sending_list_id": 88,
"timestamp": "2026-07-20T14:03:11.000000Z",
"updated_at": "2026-07-20T14:05:00.000000Z",
"dlr_status": "delivered",
"is_terminal": true,
"is_delivered": true,
"error_code": null,
"error_description": null,
"provider_message_id": "O3iFf1Eu",
"reply_id": null,
"replied_at": null,
"reply_text": null,
"reply_kind": null,
"api_key_id": "66f1c2a9b8e4d3f0a1b2c3d4",
"cost_state": "billed",
"external_ids": { "external_location_id": "loc_west" }
}
],
"pagination": { "limit": 200, "has_more": true, "next_cursor": "MjAyNi0wNy..." }
}
Error Codes
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | invalid_param | A query parameter is malformed (e.g. a naive datetime, bad status, bad cursor). |
| 500 | query_failed | The query could not be executed (transient); retry. |
401 invalid_credentials.
Next Steps
Reconciliation
Keep a local mirror in sync with the watermark.
Get a message
Fetch a single message by ID.