Message Log API

Read the delivery log: one record for every message Stoked sent or received on your community’s behalf, with its channel, what triggered it, and whether it was delivered. This is the same log as Operations > Message Log in the admin portal. It covers notifications, reminders and verification codes as well as the texts relayed between advocates and prospects.

Requires the message_log permission. The phone number or email address on each side of a message needs message_log:pii as well. The text of the message is never served: conversation messages are in the Conversations API, and everything else is a notification.

Endpoint Returns
GET /api/v1/message_log A paginated list of log records
GET /api/v1/message_log/:id One log record

List the message log

GET https://integrations.stokedhq.com/api/v1/message_log

With no filters, the list contains every record, including history imported from the messaging provider from before Stoked kept its own log. See Backfilled records.

Parameters

Parameter Description
filter[status] One or more of queued, sent, delivered, undelivered, failed, rejected, received, separated by commas: filter[status]=failed,undelivered
filter[channel] One or more of sms, whatsapp, web, email
filter[direction] outbound (Stoked sent it) or inbound (Stoked received it)
filter[trigger] One or more trigger values: filter[trigger]=phone_verification_code
filter[data_source] live or backfilled
filter[conversation] Only records that belong to this conversation, by conversation id
filter[advocate] Only records this advocate received or sent, by advocate id
filter[prospect] Only records this prospect received or sent, by prospect id
filter[created_since] Only records created at or after this time
filter[updated_since] Only records updated at or after this time. Use this for incremental syncing.
page[size] Records per page. Defaults to 50, maximum 200.
page[after] The cursor from a previous response’s links.next. See Pagination.
page[number] A page to jump to. Defaults to 1.

An unknown value for a list filter, or an id that doesn’t belong to your community, returns a 400 error rather than an empty list. Any other parameter returns 400. Records come back least recently updated first.

Request

GET /api/v1/message_log

cURL

curl -G "https://integrations.stokedhq.com/api/v1/message_log" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/vnd.api+json" \
--data-urlencode "filter[updated_since]=2026-08-01T00:00:00Z"

Ruby

require "net/http"
require "json"
uri = URI("https://integrations.stokedhq.com/api/v1/message_log")
uri.query = URI.encode_www_form("filter[updated_since]" => "2026-08-01T00:00:00Z")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
request["Accept"] = "application/vnd.api+json"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)["data"]

Python

import requests
response = requests.get(
"https://integrations.stokedhq.com/api/v1/message_log",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/vnd.api+json",
},
params={"filter[updated_since]": "2026-08-01T00:00:00Z"},
)
response.raise_for_status()
print(response.json()["data"])

C#

using System.Net.Http.Headers;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_API_KEY");
client.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.api+json");
using var response = await client.GetAsync("https://integrations.stokedhq.com/api/v1/message_log?filter[updated_since]=2026-08-01T00:00:00Z");
response.EnsureSuccessStatusCode();
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine(document.RootElement.GetProperty("data"));

JavaScript

const url = new URL("https://integrations.stokedhq.com/api/v1/message_log");
url.searchParams.set("filter[updated_since]", "2026-08-01T00:00:00Z");
const response = await fetch(url, {
headers: {
Authorization: "Bearer YOUR_API_KEY",
Accept: "application/vnd.api+json",
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const { data } = await response.json();
console.log(data);

Response

Response message-log-index.json Download
{
"data": [
{
"type": "message_log_entries",
"id": "01k5examplemessagel0g00001",
"attributes": {
"channel": "sms",
"direction": "outbound",
"trigger": "phone_verification_code",
"status": "failed",
"status_reason": "delivery_error",
"provider_error_code": "30006",
"data_source": "live",
"media_count": 0,
"to_address": "+12025550101",
"from_address": "+12025550199",
"queued_at": "2026-08-31T09:00:00Z",
"sent_at": null,
"delivered_at": null,
"failed_at": "2026-08-31T09:00:03Z",
"received_at": null,
"last_status_at": "2026-08-31T09:00:03Z",
"created_at": "2026-08-31T09:00:00Z",
"updated_at": "2026-08-31T09:00:00Z"
},
"relationships": {
"recipient": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
},
"sender": {
"data": null
},
"conversation": {
"data": null
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00001"
},
"meta": {
"pii": true
}
},
{
"type": "message_log_entries",
"id": "01k5examplemessagel0g00002",
"attributes": {
"channel": "sms",
"direction": "outbound",
"trigger": "message_sent_notification_to_prospect",
"status": "delivered",
"status_reason": null,
"provider_error_code": null,
"data_source": "live",
"media_count": 0,
"to_address": "+12025550101",
"from_address": "+12025550199",
"queued_at": "2026-09-01T15:10:00Z",
"sent_at": "2026-09-01T15:10:01Z",
"delivered_at": "2026-09-01T15:10:04Z",
"failed_at": null,
"received_at": null,
"last_status_at": "2026-09-01T15:10:04Z",
"created_at": "2026-09-01T15:10:00Z",
"updated_at": "2026-09-01T15:10:00Z"
},
"relationships": {
"recipient": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
},
"sender": {
"data": null
},
"conversation": {
"data": {
"type": "conversations",
"id": "01k5examplec0nversat10n001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/conversations/01k5examplec0nversat10n001"
}
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00002"
},
"meta": {
"pii": true
}
},
{
"type": "message_log_entries",
"id": "01k5examplemessagel0g00003",
"attributes": {
"channel": "sms",
"direction": "inbound",
"trigger": "inbound_relay",
"status": "received",
"status_reason": null,
"provider_error_code": null,
"data_source": "live",
"media_count": 0,
"to_address": "+12025550199",
"from_address": "+12025550101",
"queued_at": null,
"sent_at": null,
"delivered_at": null,
"failed_at": null,
"received_at": "2026-09-01T15:20:00Z",
"last_status_at": "2026-09-01T15:20:00Z",
"created_at": "2026-09-01T15:20:00Z",
"updated_at": "2026-09-01T15:20:00Z"
},
"relationships": {
"recipient": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
},
"sender": {
"data": null
},
"conversation": {
"data": {
"type": "conversations",
"id": "01k5examplec0nversat10n001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/conversations/01k5examplec0nversat10n001"
}
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00003"
},
"meta": {
"pii": true
}
}
],
"links": {
"self": "https://integrations.stokedhq.com/api/v1/message_log?filter%5Bupdated_since%5D=2026-08-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50",
"first": "https://integrations.stokedhq.com/api/v1/message_log?filter%5Bupdated_since%5D=2026-08-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50",
"prev": null,
"next": null,
"last": "https://integrations.stokedhq.com/api/v1/message_log?filter%5Bupdated_since%5D=2026-08-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50"
},
"meta": {
"page": 1,
"per_page": 50,
"total_count": 3,
"total_pages": 1
}
}

Get a log record

GET https://integrations.stokedhq.com/api/v1/message_log/:id

Returns the same record as the list. An ID that doesn’t exist in your community returns 404. This example was fetched with a key that has message_log but not message_log:pii, so it carries no addresses.

Request

GET /api/v1/message_log/01k5examplemessagel0g00001

cURL

curl "https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00001" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/vnd.api+json"

Ruby

require "net/http"
require "json"
uri = URI("https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00001")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
request["Accept"] = "application/vnd.api+json"
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(request) }
raise "HTTP #{response.code}: #{response.body}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)["data"]

Python

import requests
response = requests.get(
"https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00001",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/vnd.api+json",
},
)
response.raise_for_status()
print(response.json()["data"])

C#

using System.Net.Http.Headers;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "YOUR_API_KEY");
client.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.api+json");
using var response = await client.GetAsync("https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00001");
response.EnsureSuccessStatusCode();
using var document = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
Console.WriteLine(document.RootElement.GetProperty("data"));

JavaScript

const url = new URL("https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00001");
const response = await fetch(url, {
headers: {
Authorization: "Bearer YOUR_API_KEY",
Accept: "application/vnd.api+json",
},
});
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const { data } = await response.json();
console.log(data);

Response

Response message-log-show-without-pii.json Download
{
"data": {
"type": "message_log_entries",
"id": "01k5examplemessagel0g00001",
"attributes": {
"channel": "sms",
"direction": "outbound",
"trigger": "phone_verification_code",
"status": "failed",
"status_reason": "delivery_error",
"provider_error_code": "30006",
"data_source": "live",
"media_count": 0,
"queued_at": "2026-08-31T09:00:00Z",
"sent_at": null,
"delivered_at": null,
"failed_at": "2026-08-31T09:00:03Z",
"received_at": null,
"last_status_at": "2026-08-31T09:00:03Z",
"created_at": "2026-08-31T09:00:00Z",
"updated_at": "2026-08-31T09:00:00Z"
},
"relationships": {
"recipient": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
},
"sender": {
"data": null
},
"conversation": {
"data": null
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/message_log/01k5examplemessagel0g00001"
},
"meta": {
"pii": false
}
}
}

Attributes

Records with "type": "message_log_entries".

Attribute Type Personal data Description
channel string   sms, whatsapp, web (a message typed on a Stoked site), or email
direction string   outbound or inbound
trigger string   What caused the message. See Triggers.
status string   Where delivery stands. See Statuses.
status_reason string or null   Why a message was not delivered, in Stoked’s words, for example blocked_by_opt_out when the recipient had texted STOP. null for delivered messages.
provider_error_code string or null   The messaging provider’s own error code, when it gave one. Twilio’s codes are documented at twilio.com.
data_source string   live, or backfilled for imported history. See Backfilled records.
media_count integer   Number of images or other attachments
to_address string Yes The phone number (E.164) or email address the message went to
from_address string Yes The phone number or email address it came from
queued_at timestamp or null   When Stoked handed the message to the provider
sent_at timestamp or null   When the provider sent it
delivered_at timestamp or null   When the recipient’s carrier confirmed delivery
failed_at timestamp or null   When delivery failed or the message was rejected
received_at timestamp or null   When an inbound message arrived
last_status_at timestamp   When the status last changed. The most useful single timestamp.
created_at timestamp    
updated_at timestamp   Moves whenever the status changes, so a message that is sent in one sync and delivered in the next is picked up by filter[updated_since].

Attributes marked Personal data appear only for keys with message_log:pii, and are left out otherwise. The record’s meta.pii says which shape you received. A web message has no phone numbers: its two addresses are the participants’ names.

Statuses

Status Meaning
queued Handed to the provider, not yet sent
sent Sent by the provider, delivery not yet confirmed. Some carriers never confirm, so a message can stay sent forever and still have arrived.
delivered Delivery confirmed by the recipient’s carrier
undelivered The carrier could not deliver it (wrong number, landline, carrier filtering). provider_error_code says why.
failed The provider could not send it
rejected Stoked did not send it, for a reason in status_reason, for example the recipient had opted out
received An inbound message Stoked received

Triggers

trigger values are lower-case with underscores. The ones you will see most:

Trigger What it is
conversation_message A message typed on a Stoked site (channel is web)
inbound_relay A text from an advocate or prospect that Stoked relayed to the other party
inbound_unrouted A text from a number Stoked could not match to a conversation
message_sent_notification_to_prospect, message_sent_notification_to_advocate The text that carries a new message to the other party
new_conversation_notification_to_prospect, new_conversation_notification_to_advocate A conversation started
conversation_reminder_to_prospect, conversation_reminder_to_advocate A nudge about an unanswered conversation
verification_reminder_to_prospect A nudge to finish verifying contact details
phone_verification_code, email_verification_code A verification code
waiver_signed_notification_to_advocate A prospect signed a waiver
advocate_welcome_message Sent when an advocate is activated
advocate_wallet_credited_notification, advocate_wallet_debited_notification Reward points changed
backfilled_outbound, backfilled_inbound Imported history, where the trigger was not recorded

New triggers are added as Stoked gains features; treat an unfamiliar value as one you have not seen yet, not as an error.

Relationships

Relationship Type Description
recipient advocates or prospects The person the record is filed under. For an outbound message, who it was sent to. For an inbound text, the person who sent it. null for texts from unknown numbers. Requires the matching permission to follow.
sender advocates, prospects, or admins Who wrote a web message. null for texts and automated messages. Admins carry no link, as there is no admins endpoint.
conversation conversations The conversation the message belongs to. null for messages about something else (a verification code, a reward, an introduction) and for texts imported from the provider. Requires the conversations permission to follow.

A record never carries a person’s details, only their type, id, and link.

Backfilled records

When Stoked began keeping this log, earlier messages were imported so the history is complete. Those records have data_source of backfilled. Texts imported from the messaging provider carry a trigger of backfilled_outbound or backfilled_inbound, a recipient where the phone number matched an advocate or prospect, and no conversation, because the provider does not know which conversation a text belonged to. Web messages imported from Stoked’s own history keep their conversation_message trigger and their conversation. Addresses and timestamps are real in both cases. Use filter[data_source]=live to leave imported records out.


© 2024-2026 Stoked — Real conversations. Real trust.