Conversations API
Read the conversations between your advocates and prospects, including tags, the AI summary, the resolution, every message, and every beat — the system-recorded moments on the timeline, like a signed waiver or a blocked prospect.
Requires the conversations permission.
| Endpoint | Returns |
|---|---|
GET /api/v1/conversations |
A paginated list of conversations, each with all of its messages and beats |
GET /api/v1/conversations/:id |
One conversation with all of its messages and beats |
List conversations
GET https://integrations.stokedhq.com/api/v1/conversations
With no filters, the list contains every conversation in your community — pending, active, and closed. This differs from the admin portal’s Conversations list, which hides closed conversations by default.
Parameters
| Parameter | Description |
|---|---|
filter[status] |
One or more of pending, active, closed, separated by commas: filter[status]=pending,active |
filter[created_since] |
Only conversations created at or after this time |
filter[updated_since] |
Only conversations updated at or after this time. Use this for incremental syncing. |
filter[advocate] |
Only conversations with this advocate, by advocate id |
filter[prospect] |
Only conversations with this prospect, by prospect id |
page[size] |
Conversations 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. |
Time filters accept an ISO 8601 timestamp with a time zone (2026-09-01T00:00:00Z) or a date (2026-09-01, read as midnight UTC). Both are inclusive.
filter[advocate] and filter[prospect] can be combined with each other and with the other filters. An id that doesn’t belong to an advocate or prospect in your community returns a 400 error rather than an empty list. An advocate or prospect record links straight to its filtered list in relationships.conversations.links.related.
Any other parameter — including sort — returns a 400 error. Conversations always come back least recently updated first.
Request
cURL
curl -G "https://integrations.stokedhq.com/api/v1/conversations" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/vnd.api+json" \
--data-urlencode "filter[status]=active,closed" \
--data-urlencode "filter[updated_since]=2026-09-01T00:00:00Z"
Ruby
require "net/http"
require "json"
uri = URI("https://integrations.stokedhq.com/api/v1/conversations")
uri.query = URI.encode_www_form("filter[status]" => "active,closed", "filter[updated_since]" => "2026-09-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/conversations",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "application/vnd.api+json",
},
params={"filter[status]": "active,closed", "filter[updated_since]": "2026-09-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/conversations?filter[status]=active,closed&filter[updated_since]=2026-09-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/conversations");
url.searchParams.set("filter[status]", "active,closed");
url.searchParams.set("filter[updated_since]", "2026-09-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
{
"data": [
{
"type": "conversations",
"id": "01k5examplec0nversat10n001",
"attributes": {
"status": "closed",
"messages_count": 4,
"beats_count": 1,
"tags": [
"reason/test-ride"
],
"created_at": "2026-09-01T15:00:00Z",
"updated_at": "2026-09-06T17:30:00Z",
"activated_at": "2026-09-01T15:00:00Z",
"closed_at": "2026-09-06T17:30:00Z",
"last_message_at": "2026-09-01T15:20:00Z",
"first_advocate_reply_seconds": 300,
"summary": {
"who_and_what": "Mike asked Sarah about trying her cargo bike for school drop-off. They arranged a Saturday test ride.",
"status": "Closed after a test ride.",
"prospect_sentiment": 0.8,
"prospect_sentiment_level": "positive",
"advocate_sentiment": 0.6,
"advocate_sentiment_level": "positive"
},
"resolution": {
"close_reason": "Test ride completed",
"close_reason_id": "01k5examplecl0sereas0n0001",
"note": "Mike took the test ride and plans to order.",
"closed_at": "2026-09-06T17:30:00Z",
"closed_by": "admin"
}
},
"relationships": {
"advocate": {
"data": {
"type": "advocates",
"id": "01k5exampleadv0cate0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001"
}
},
"prospect": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
},
"messages": {
"data": [
{
"type": "messages",
"id": "900001"
},
{
"type": "messages",
"id": "900002"
},
{
"type": "messages",
"id": "900003"
},
{
"type": "messages",
"id": "900004"
}
]
},
"timeline": {
"data": [
{
"type": "messages",
"id": "900001"
},
{
"type": "messages",
"id": "900002"
},
{
"type": "beats",
"id": "01k5examplebeat00000000001"
},
{
"type": "messages",
"id": "900003"
},
{
"type": "messages",
"id": "900004"
}
]
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/conversations/01k5examplec0nversat10n001"
}
}
],
"included": [
{
"type": "messages",
"id": "900001",
"attributes": {
"content": "Hi Sarah! I'm thinking about a cargo bike for school drop-off. Could I try yours?",
"status": "active",
"source": "sms_relay",
"created_at": "2026-09-01T15:05:00Z",
"blocked_at": null,
"moderation": {
"flagged": false,
"categories": []
}
},
"relationships": {
"sender": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
}
}
},
{
"type": "messages",
"id": "900002",
"attributes": {
"content": "Absolutely! I'm free Saturday morning. It fits both of my kids plus groceries.",
"status": "active",
"source": "sms_relay",
"created_at": "2026-09-01T15:10:00Z",
"blocked_at": null,
"moderation": {
"flagged": false,
"categories": []
}
},
"relationships": {
"sender": {
"data": {
"type": "advocates",
"id": "01k5exampleadv0cate0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001"
}
}
}
},
{
"type": "messages",
"id": "900003",
"attributes": {
"content": "Hi both, Alex from the team here. Mike, remember to bring a helmet on Saturday!",
"status": "active",
"source": "web",
"created_at": "2026-09-01T15:15:00Z",
"blocked_at": null,
"moderation": {
"flagged": false,
"categories": []
},
"sender_name": "Alex Placeholder"
},
"relationships": {
"sender": {
"data": {
"type": "admins",
"id": "01k5exampleadm1n0000000001"
}
}
}
},
{
"type": "messages",
"id": "900004",
"attributes": {
"content": "Perfect, see you Saturday at 10. Thanks!",
"status": "active",
"source": "sms_relay",
"created_at": "2026-09-01T15:20:00Z",
"blocked_at": null,
"moderation": {
"flagged": false,
"categories": []
}
},
"relationships": {
"sender": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
}
}
},
{
"type": "beats",
"id": "01k5examplebeat00000000001",
"attributes": {
"kind": "waiver_signed",
"summary": "Mike Sample signed the Liability Waiver",
"created_at": "2026-09-01T15:12:00Z"
},
"relationships": {
"subject": {
"data": {
"type": "waiver_submissions",
"id": "01k5examplewa1vers1gn00001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/waiver_submissions/01k5examplewa1vers1gn00001"
}
}
}
}
],
"links": {
"self": "https://integrations.stokedhq.com/api/v1/conversations?filter%5Bupdated_since%5D=2026-09-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50",
"first": "https://integrations.stokedhq.com/api/v1/conversations?filter%5Bupdated_since%5D=2026-09-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50",
"prev": null,
"next": null,
"last": "https://integrations.stokedhq.com/api/v1/conversations?filter%5Bupdated_since%5D=2026-09-01T00%3A00%3A00Z&page%5Bnumber%5D=1&page%5Bsize%5D=50"
},
"meta": {
"page": 1,
"per_page": 50,
"total_count": 1,
"total_pages": 1,
"close_reasons": [
{
"id": "01k5examplecl0sereas0n0001",
"name": "Test ride completed",
"system": false
}
]
}
}
Every conversation on the page comes with all of its messages and beats:
relationships.timeline— thetypeandidof every message and beat in the conversation, oldest first, in the same order the admin portal timeline shows them. Walk this to render a transcript. See Beat attributes below.relationships.messages— thetypeandidof each of its messages only, oldest first. Unchanged: it never includes beats.- A top-level
includedarray with every message and beat on the page in full
A full sync is therefore a handful of list pages, not one request per conversation. Match a message to its conversation through relationships.messages or relationships.timeline; match a beat through relationships.timeline — there is no relationships.beats, so to collect just the beats, filter included on type == "beats".
The response’s top-level meta.close_reasons lists your community’s close reasons (id, name, and whether it is a built-in system reason), so you can key a resolution by close_reason_id without another request.
Get a conversation
GET https://integrations.stokedhq.com/api/v1/conversations/:id
Returns the same conversation record as the list, with its messages and beats in included. An ID that doesn’t exist in your community returns 404.
Request
cURL
curl "https://integrations.stokedhq.com/api/v1/conversations/01k5examplec0nversat10n001" \
-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/conversations/01k5examplec0nversat10n001")
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/conversations/01k5examplec0nversat10n001",
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/conversations/01k5examplec0nversat10n001");
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/conversations/01k5examplec0nversat10n001");
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
{
"data": {
"type": "conversations",
"id": "01k5examplec0nversat10n001",
"attributes": {
"status": "closed",
"messages_count": 4,
"beats_count": 1,
"tags": [
"reason/test-ride"
],
"created_at": "2026-09-01T15:00:00Z",
"updated_at": "2026-09-06T17:30:00Z",
"activated_at": "2026-09-01T15:00:00Z",
"closed_at": "2026-09-06T17:30:00Z",
"last_message_at": "2026-09-01T15:20:00Z",
"first_advocate_reply_seconds": 300,
"summary": {
"who_and_what": "Mike asked Sarah about trying her cargo bike for school drop-off. They arranged a Saturday test ride.",
"status": "Closed after a test ride.",
"prospect_sentiment": 0.8,
"prospect_sentiment_level": "positive",
"advocate_sentiment": 0.6,
"advocate_sentiment_level": "positive"
},
"resolution": {
"close_reason": "Test ride completed",
"close_reason_id": "01k5examplecl0sereas0n0001",
"note": "Mike took the test ride and plans to order.",
"closed_at": "2026-09-06T17:30:00Z",
"closed_by": "admin"
}
},
"relationships": {
"advocate": {
"data": {
"type": "advocates",
"id": "01k5exampleadv0cate0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001"
}
},
"prospect": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
},
"messages": {
"data": [
{
"type": "messages",
"id": "900001"
},
{
"type": "messages",
"id": "900002"
},
{
"type": "messages",
"id": "900003"
},
{
"type": "messages",
"id": "900004"
}
]
},
"timeline": {
"data": [
{
"type": "messages",
"id": "900001"
},
{
"type": "messages",
"id": "900002"
},
{
"type": "beats",
"id": "01k5examplebeat00000000001"
},
{
"type": "messages",
"id": "900003"
},
{
"type": "messages",
"id": "900004"
}
]
}
},
"links": {
"self": "https://integrations.stokedhq.com/api/v1/conversations/01k5examplec0nversat10n001"
}
},
"included": [
{
"type": "messages",
"id": "900001",
"attributes": {
"content": "Hi Sarah! I'm thinking about a cargo bike for school drop-off. Could I try yours?",
"status": "active",
"source": "sms_relay",
"created_at": "2026-09-01T15:05:00Z",
"blocked_at": null,
"moderation": {
"flagged": false,
"categories": []
}
},
"relationships": {
"sender": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
}
}
},
{
"type": "messages",
"id": "900002",
"attributes": {
"content": "Absolutely! I'm free Saturday morning. It fits both of my kids plus groceries.",
"status": "active",
"source": "sms_relay",
"created_at": "2026-09-01T15:10:00Z",
"blocked_at": null,
"moderation": {
"flagged": false,
"categories": []
}
},
"relationships": {
"sender": {
"data": {
"type": "advocates",
"id": "01k5exampleadv0cate0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/advocates/01k5exampleadv0cate0000001"
}
}
}
},
{
"type": "messages",
"id": "900003",
"attributes": {
"content": "Hi both, Alex from the team here. Mike, remember to bring a helmet on Saturday!",
"status": "active",
"source": "web",
"created_at": "2026-09-01T15:15:00Z",
"blocked_at": null,
"moderation": {
"flagged": false,
"categories": []
},
"sender_name": "Alex Placeholder"
},
"relationships": {
"sender": {
"data": {
"type": "admins",
"id": "01k5exampleadm1n0000000001"
}
}
}
},
{
"type": "messages",
"id": "900004",
"attributes": {
"content": "Perfect, see you Saturday at 10. Thanks!",
"status": "active",
"source": "sms_relay",
"created_at": "2026-09-01T15:20:00Z",
"blocked_at": null,
"moderation": {
"flagged": false,
"categories": []
}
},
"relationships": {
"sender": {
"data": {
"type": "prospects",
"id": "01k5examplepr0spect0000001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/prospects/01k5examplepr0spect0000001"
}
}
}
},
{
"type": "beats",
"id": "01k5examplebeat00000000001",
"attributes": {
"kind": "waiver_signed",
"summary": "Mike Sample signed the Liability Waiver",
"created_at": "2026-09-01T15:12:00Z"
},
"relationships": {
"subject": {
"data": {
"type": "waiver_submissions",
"id": "01k5examplewa1vers1gn00001"
},
"links": {
"related": "https://integrations.stokedhq.com/api/v1/waiver_submissions/01k5examplewa1vers1gn00001"
}
}
}
}
]
}
Conversation attributes
Records with "type": "conversations".
| Attribute | Type | Description |
|---|---|---|
status |
string | pending (the prospect hasn’t verified their contact details yet), active, or closed |
messages_count |
integer | Number of messages in the conversation |
beats_count |
integer | Number of beats in the conversation |
tags |
array of strings | The tags applied to the conversation. Each tag is given as its path: the tag’s group, a slash, then the tag’s name, for example model/q45. A tag that was never put in a group is in the general group, so it reads general/vip. |
created_at |
timestamp | When the prospect started the conversation |
updated_at |
timestamp | When the conversation last changed. See What changes updated_at. |
activated_at |
timestamp or null | When the conversation became active |
closed_at |
timestamp or null | When the conversation was closed. null while it is open. A few conversations closed before April 2025 have a closed_at but no resolution, because closing did not record a reason yet. |
last_message_at |
timestamp or null | When the most recent message was sent |
first_advocate_reply_seconds |
integer or null | How long the advocate took to first reply to the prospect, in active-hours seconds. null until the advocate has replied. See First reply time. |
summary |
object or null | The AI-generated summary and sentiment. null until one has been generated, or when AI summaries are turned off for the community. |
resolution |
object or null | How the conversation was most recently closed. null if it has never been closed with a reason. |
The summary object
| Field | Type | Description |
|---|---|---|
who_and_what |
string | Who is talking and what about |
status |
string | Where the conversation stands now |
prospect_sentiment |
number or null | The prospect’s overall tone across the conversation so far, from -1.0 (negative) to 1.0 (positive). null if the prospect hasn’t said enough to judge. |
prospect_sentiment_level |
string or null | positive (score of 0.33 or more), neutral, or negative (score of -0.33 or less). The same bands the admin portal shows. null when the score is. |
advocate_sentiment |
number or null | The advocate’s overall tone, on the same scale |
advocate_sentiment_level |
string or null | The advocate’s band, on the same scale |
The summary is regenerated about ten minutes after the last message in a burst, so a brand-new conversation has no summary and a busy one can lag by a few messages. Sentiment describes the whole conversation to date, not the latest message.
The resolution object
| Field | Type | Description |
|---|---|---|
close_reason |
string | The name of the close reason that was selected. Close reasons are defined by your community in Settings, so the names are yours: don’t hard-code the example’s. |
close_reason_id |
string | The close reason’s id. Stable across renames, so key on this. The list’s meta.close_reasons maps ids to current names. |
note |
string or null | The optional note entered when closing |
closed_at |
timestamp | When this resolution was recorded |
closed_by |
string | Who closed it: admin, advocate, prospect, or system |
First reply time
first_advocate_reply_seconds is the time from the prospect’s first message to the advocate’s first reply after it, for this one conversation. It is measured exactly as the Reply Speed report measures a first reply, so the two agree:
- Seconds are active-hours seconds, not wall-clock seconds. The clock only runs during your community’s active hours, read in the advocate’s own time zone. To get the wall-clock figure, subtract the two messages’
created_atvalues yourself. - A message the prospect wrote before verifying starts the clock at
activated_at, when the advocate could first see it. - When the message and the reply both fall inside the same off-hours stretch, wall-clock seconds are used.
- An opening message from the advocate, before the prospect has written, is not a reply.
The value is worked out when you request it, from your community’s current active hours and the advocate’s current time zone. Changing either changes the value without moving the conversation’s updated_at.
Relationships
| Relationship | Type | Description |
|---|---|---|
advocate |
advocates |
The advocate in the conversation. links.related is their Advocates API URL. |
prospect |
prospects |
The prospect in the conversation. links.related is their Prospects API URL. |
messages |
messages |
Every message, oldest first. The records are in the response’s included array. |
timeline |
messages, beats |
Every message and beat in the conversation, oldest first (by created_at, then id) — the same order the admin portal timeline shows. Walk this one to render a transcript; the records are in included. |
A conversation never contains the advocate’s or prospect’s details, only their
type,id, and link. Fetching the link requires theadvocatesorprospectspermission; a key with onlyconversationsgets a403there.
What changes updated_at
A conversation’s updated_at moves forward whenever anything in its API record changes:
- A message arrives, or a message’s status changes (for example, it is flagged or blocked)
- A beat is recorded (a waiver is signed, a prospect is blocked or unblocked, someone opts in or out of SMS, an offer is viewed or clicked, a private event is scheduled, cancelled, or completed)
- The AI summary is created, updated, or removed. Turning AI summaries off for the community deletes its summaries, so every conversation that had one shows as updated once and its
summarybecomesnull. - The conversation is activated, closed, resolved, or reopened
- A tag is added, removed, renamed, or reordered. Renaming a tag, moving it to another group, or renaming its group changes its path and marks the conversation as updated, so
filter[updated_since]picks it up.
Message attributes
Records with "type": "messages". Messages appear in the included array of a list page or a single conversation, never on their own.
| Attribute | Type | Description |
|---|---|---|
content |
string | The text of the message |
status |
string | active for a normal message. Otherwise pending (still being processed), flagged (held for moderation review), blocked (stopped by moderation), or failed. |
source |
string | How the message was sent: sms_relay (by text message) or web (from a Stoked site) |
created_at |
timestamp | When the message was sent |
blocked_at |
timestamp or null | When the message was blocked. null for messages that weren’t blocked. |
moderation |
object or null | The automated moderation result: flagged (boolean) and categories (array of strings, empty when nothing was found). null if the message hasn’t been checked yet. |
sender_name |
string | Only present when the sender is not the advocate or prospect: the admin’s name, or System for automated messages |
Flagged and blocked messages are included so your copy matches the full transcript. Check status and blocked_at if you only want messages the other person actually received.
Every message is checked by OpenAI’s moderation model as it arrives. moderation.categories uses that model’s category names, for example harassment or self-harm. A message with flagged: true also has status: "flagged"; the categories tell you why.
Relationships
| Relationship | Type | Description |
|---|---|---|
sender |
advocates, prospects, or admins |
Who sent the message. null for automated system messages. Compare the id with the conversation’s advocate and prospect to tell who is speaking. Advocate and prospect senders carry a links.related URL; admins don’t, as there is no admins endpoint. |
Beat attributes
Records with "type": "beats". A beat is a system-recorded moment on the conversation timeline — something that happened rather than something someone said: a waiver signed, a prospect blocked, an SMS opt-out, an offer viewed. See Conversation Beats for what these look like in the admin portal.
Beats appear in the included array of a list page or a single conversation, never on their own, and only from when beats shipped in the product (May 2026) forward. A waiver signed before then is an ordinary message on the timeline, not a beat.
| Attribute | Type | Description |
|---|---|---|
kind |
string | What happened. See Beat kinds below. |
summary |
string | The human-readable sentence the admin timeline shows, for example “Mike Sample signed the Test Ride Waiver.” Never gated by a :pii permission — same rule as message content. |
created_at |
timestamp | When the event happened |
Beat kinds
| Kind | What happened |
|---|---|
waiver_signed |
A prospect signed a waiver |
prospect_blocked_by_advocate |
The advocate blocked the prospect |
prospect_unblocked_by_advocate |
The advocate unblocked the prospect |
prospect_blocked_by_admin |
An admin blocked the prospect |
prospect_unblocked_by_admin |
An admin unblocked the prospect |
prospect_sms_opted_out |
The prospect opted out of SMS, for example by texting STOP |
prospect_sms_opted_in |
The prospect opted back into SMS |
advocate_sms_opted_out |
The advocate opted out of SMS |
advocate_sms_opted_in |
The advocate opted back into SMS |
offer_viewed |
The prospect opened a shared offer page from this conversation |
offer_clicked |
The prospect clicked the offer page’s Shop button |
private_event_scheduled |
A private event (for example a test ride) was scheduled |
private_event_cancelled |
A private event was cancelled |
private_event_completion_recorded |
A private event was marked complete |
This list grows over time as new kinds of beats ship. Treat kind as open-ended: tolerate a value you don’t recognize instead of erroring, and fall back to summary for anything you don’t special-case.
Relationships
| Relationship | Type | Description |
|---|---|---|
subject |
varies | Only present when the beat’s subject has its own endpoint. A waiver_signed beat points at its waiver submission; prospect_sms_opted_out / prospect_sms_opted_in point at the prospect; advocate_sms_opted_out / advocate_sms_opted_in and prospect_blocked_by_advocate / prospect_unblocked_by_advocate point at the advocate (the one who opted out, blocked or unblocked). Every other kind carries no subject — read summary instead. |
links.relatedonsubjectis a pointer, not a grant: following it needs the matching permission (waivers,prospects, oradvocates), the same as any other relationship link in this API.
Beats about private events and offers don’t have their own endpoint yet, so
summaryis currently the only detail available for them. Adetailsobject (event dates, completion status) and a private events endpoint are planned.