Event History
Every answer, reply, completion, and submission is an event on your account's stream. sp events, sp collect, and the SDK streams all read this stream. You can also read it raw when you want the events themselves rather than a handle for one send.
Reading the stream
sp events prints the raw feed. In the SDKs it is client.events() on both Client and OrgClient. Both stream live by default. To replay history first, pass --since on the CLI (24h, 7d, or ISO 8601). Add --follow to keep streaming once the replay has caught up.
sp events --since 24h --followYour personal stream carries the events for everything you sent. Answers, replies, and completions go to the sender. The stream also carries submissions created on your own account. An organization API key reads the organization's stream instead.
The event envelope
{
"streamId": "…",
"version": 42,
"eventType": "TaskInputCompleted",
"createdAt": "2026-07-19T12:00:00Z",
"actor": {
"publicId": "usr_…",
"name": "Alice",
"devicePublicId": "dev_…",
"deviceName": "Alice's iPhone"
},
"data": {
"type": "taskInputCompleted",
"taskId": "tsk_…",
"inputUploaded": {
"type": "choiceSelected",
"id": "inp_…",
"selectedIndex": 0,
"selectedValue": "Approve"
}
}
}| Field | Description |
|---|---|
streamId | The stream the event belongs to. |
version | Sequential number within the stream, used for ordering and exact resumption. |
eventType | PascalCase type, see below. |
actor | Who acted, with display name and device. Present on receive-side events (someone answered, replied, or submitted). Absent on collective events. |
data | Type-specific payload, discriminated by a camelCase type. |
encryption | { "type": "personal", "keyFingerprint": … } or { "type": "org", "v": … } when the payload is encrypted. Absent for plaintext. |
createdAt | ISO 8601 timestamp. |
Optional fields are omitted from the JSON when empty, never null. All entity ids are prefixed (tsk_, sub_, ntf_, sbm_, inp_, usr_, dev_, grptsk_, grpntf_). Treat them as opaque strings.
Event types
eventType | Emitted when |
|---|---|
TaskInputUploaded | An input answer arrived (auto-commit off: not yet committed). |
TaskInputCompleted | An input was answered and committed. |
TaskCompleted | All required inputs are done (or the task was completed manually). data.inputsUploaded carries every answer. |
TaskDeletedByRecipient / TaskDeleted | The task was removed before completion. |
SubtaskInputUploaded / SubtaskInputCompleted / SubtaskCompleted | The same lifecycle for an appended subtask, with subtaskId and parentTaskId. |
ReplyAppended | A reply-composer reply: text, photo, file, audio, or location. |
NotificationCompleted | A notification input was answered (data.reply is the text, choice, or action reply). |
SubmissionCreated | Someone sent a submission from the app: any of text, photo, file, audio, location in one event. |
The inputUploaded payload varies by input kind:
type | Fields |
|---|---|
textUploaded | id, value |
choiceSelected | id, selectedIndex, selectedValue |
multiChoiceSelected | id, selectedIndices, selectedValues (parallel arrays; may be empty for an optional input) |
actionSelected | id, selectedKey |
sliderUploaded | id, value |
photoUploaded / fileUploaded | id, contentType, checksumSha256, size (+ filename for files) |
voiceRecorded | id, contentType, checksumSha256, size, durationSeconds |
locationUploaded | id, latitude, longitude, accuracy, altitude, heading, speed, timestamp, or a single encrypted blob when the task is end-to-end encrypted |
Waiting on a single send
Besides the account-wide stream, each send can be observed on its own with its wait token. Every send returns one. The token replays the events of that one send and streams new ones until the send completes. It grants no other access on the account.
Wait: true on a curl send and sp task --wait use the wait token. It also lets a process that holds only the wait token, and not your API token, safely observe one task and nothing else.