curl -X POST https://api.tracklysms.com/api/v2/pending-sends \
-H "X-Api-Key: trk_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reservation-confirmation-1042-v1" \
-d '{
"message": {
"to": "+14155551234",
"list_number": "+18005551234",
"body": "Hi Sam, your table for 4 is confirmed for 7pm tonight. Reply STOP to opt out."
},
"note": "Reservation confirmation requested in chat at 2:10pm"
}'
{
"id": "66f1c2a9b8e4d3f0a1b2c3d4",
"status": "pending",
"kind": "send",
"recipient_count": 1,
"note": "Reservation confirmation requested in chat at 2:10pm",
"api_key_id": "66e0a1b2c3d4e5f6a7b8c9d0",
"sandbox": false,
"payload": {
"to": "+14155551234",
"list_number": "+18005551234",
"body": "Hi Sam, your table for 4 is confirmed for 7pm tonight. Reply STOP to opt out."
},
"created_at": "2026-09-04T14:10:32.000000Z",
"expires_at": "2026-09-05T14:10:32.000000Z",
"decided_at": null,
"decided_by": null,
"decision_reason": null,
"executed_at": null,
"executed_by_key_id": null,
"result": null,
"last_error": null
}
{ "error": "This account already has 100 sends awaiting approval or execution", "code": "too_many_pending_sends" }
Pending Sends (v2)
Hold a Send for Approval
Store a send for a person to approve in the dashboard before the key sends it.
POST
/
v2
/
pending-sends
curl -X POST https://api.tracklysms.com/api/v2/pending-sends \
-H "X-Api-Key: trk_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reservation-confirmation-1042-v1" \
-d '{
"message": {
"to": "+14155551234",
"list_number": "+18005551234",
"body": "Hi Sam, your table for 4 is confirmed for 7pm tonight. Reply STOP to opt out."
},
"note": "Reservation confirmation requested in chat at 2:10pm"
}'
{
"id": "66f1c2a9b8e4d3f0a1b2c3d4",
"status": "pending",
"kind": "send",
"recipient_count": 1,
"note": "Reservation confirmation requested in chat at 2:10pm",
"api_key_id": "66e0a1b2c3d4e5f6a7b8c9d0",
"sandbox": false,
"payload": {
"to": "+14155551234",
"list_number": "+18005551234",
"body": "Hi Sam, your table for 4 is confirmed for 7pm tonight. Reply STOP to opt out."
},
"created_at": "2026-09-04T14:10:32.000000Z",
"expires_at": "2026-09-05T14:10:32.000000Z",
"decided_at": null,
"decided_by": null,
"decision_reason": null,
"executed_at": null,
"executed_by_key_id": null,
"result": null,
"last_error": null
}
{ "error": "This account already has 100 sends awaiting approval or execution", "code": "too_many_pending_sends" }
Holds a single send or a bulk batch until a person approves it. Nothing is sent by this call. The flow is:
Also
- The key calls this endpoint with the exact payload it would have sent. The response is the pending send with
status: pendingand anexpires_at24 hours out. - A person opens Settings → Send Approvals in the dashboard, reads the recipients, the message and your
note, and approves or rejects it. - An authorized same-account, same-mode key polls the pending send until
statusisapproved, then calls send. The immutable review contains rendered recipients, senders, message text, and link destinations. Allocated message and short-link IDs use display placeholders. Rendering or configuration changes require a new review before publication.
403 confirmation_required), or perform send-enabling schedule operations; this endpoint is its way to send approved SMS. Any key can use it voluntarily. Check api_key.send_policy.require_confirmation on Who Am I.
Creation validates shape, allowed lists, and the rendered review without transport or quota consumption. Suppression, double opt-in, warmup, and current key policy run on execution; billing remains downstream. A send held by a sandbox key is marked sandbox: true and can only be executed by a sandbox key, as a simulation; a live hold can only be executed by a live key.
Each account can have 100 unexpired open holds and create 500 holds in a rolling 24-hour window, shared across keys and modes. Rejection or cancellation does not refund the rolling creation limit. Admission atomically reserves capacity and the pending ID. The payload plus rendered review is limited to 4 MiB. Records contain recipient and message data and are subject to 30-day TTL retention; expiry after 24 hours prevents approval or execution but does not immediately delete the record.
Use Idempotency-Key for creation retries; MCP requires it. Within the 24-hour reservation window, the same account, mode, idempotency header value, and exact body recover the reserved hold; changed payload returns 409 idempotency_conflict. On unknown_hold, read the pending ID when returned. A network timeout may omit it: replay the same header and body, then use a bounded recent-hold lookup if needed. Never change the key or create a replacement send to resolve uncertainty.
Body Parameters
Provide exactly one ofmessage or messages.
object
One
POST /v2/send request body: to, list_number, body, and optionally wrap_links, metadata, contact_data, linkOverrides.object[]
A
POST /v2/send/bulk batch of up to 1000 messages, held and approved as one unit.string
Why this send should go out, shown to the approver next to the message. At most 500 characters.
Response Fields
string
Pending send ID; use it to poll, cancel, and send.
string
pending on creation. Later approved, executing, unknown_execution, sent, rejected, cancelled, or expired. sent means submitted, not delivered.object
Publication attempt ID, attempted and confirmed counts, and per-message evidence on detail responses;
null before execution. Uncertain outcomes require reconciliation using the same pending ID.string
send or send_bulk.integer
Messages in the held payload.
string
The note as submitted, or
null.string
The key that held it.
boolean
true when a sandbox key held it. Only a key in the same mode can execute it.object
The held payload exactly as submitted: the
message body, or { "messages": [...] }. Object keys anywhere in it (for example in metadata or contact_data.custom_fields) cannot start with $ or contain ..string
ISO-8601, UTC.
string
When an undecided or unexecuted send lapses (ISO-8601, UTC), 24 hours after creation. An expired send cannot be approved or sent.
string
When it was approved, rejected, or cancelled;
null until then.string
user:<id> for a dashboard decision, api_key:<id> for a cancel; null until then.string
The approver’s rejection reason, or
null.string
When execution was claimed;
null before a claim. This timestamp does not establish submission or delivery.string
The key that executed it;
null until then.object
The send response body once executed (
message_id and status for a single send; queued_count, error_count, errors for a batch); null before.object
The error body from the most recent failed send attempt, or
null.Errors
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | invalid_payload | Not a JSON object, neither or both of message / messages given, note is too long, or an object key in the payload starts with $ or contains .. |
| 400 | missing_to, missing_list_number, missing_body, invalid_phone, invalid_list_number, invalid_contact_data | A message fails the same shape checks as /v2/send; for a batch the message index is named in error. |
| 400 | list_not_found | A list_number is not an active sending list on this account. |
| 400 | missing_messages, invalid_messages | messages is empty or not an array. |
| 403 | list_not_allowed_for_key | A list_number is outside the key’s send_policy.list_numbers. |
| 413 | too_many_messages | More than 1000 messages in messages. |
| 413 | pending_send_too_large | Payload plus rendered review exceeds 4 MiB. |
| 409 | idempotency_conflict | The same creation key was used with a different payload. |
| 429 | too_many_pending_sends | 100 unexpired open holds or 500 holds in the rolling 24-hour window. reason distinguishes open_holds from holds_24h; retry_after and Retry-After give a retry delay. |
| 503 | pending_send_unavailable | Admission authority is unavailable. |
| 503 | unknown_hold | Persistence is uncertain; read the ID when present or replay the same Idempotency-Key and exact body. |
401 invalid_credentials. Supports the Idempotency-Key header.
Examples
curl -X POST https://api.tracklysms.com/api/v2/pending-sends \
-H "X-Api-Key: trk_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: reservation-confirmation-1042-v1" \
-d '{
"message": {
"to": "+14155551234",
"list_number": "+18005551234",
"body": "Hi Sam, your table for 4 is confirmed for 7pm tonight. Reply STOP to opt out."
},
"note": "Reservation confirmation requested in chat at 2:10pm"
}'
{
"id": "66f1c2a9b8e4d3f0a1b2c3d4",
"status": "pending",
"kind": "send",
"recipient_count": 1,
"note": "Reservation confirmation requested in chat at 2:10pm",
"api_key_id": "66e0a1b2c3d4e5f6a7b8c9d0",
"sandbox": false,
"payload": {
"to": "+14155551234",
"list_number": "+18005551234",
"body": "Hi Sam, your table for 4 is confirmed for 7pm tonight. Reply STOP to opt out."
},
"created_at": "2026-09-04T14:10:32.000000Z",
"expires_at": "2026-09-05T14:10:32.000000Z",
"decided_at": null,
"decided_by": null,
"decision_reason": null,
"executed_at": null,
"executed_by_key_id": null,
"result": null,
"last_error": null
}
{ "error": "This account already has 100 sends awaiting approval or execution", "code": "too_many_pending_sends" }