Referencing a participant's data
Content names a participant's data one way everywhere: in a condition, in a
journey's transitions, and in a {{ }} token in text. A
path reads broad to narrow — domain, item, which record, field:
sequenceResponses.SLEEP_CHECK.latest.scores.TOTAL
atomResponses.MOOD.previous
extras.QUIT_DATE.latest.daysUntil
signals.steps.today
Items are codes, records are words
- An item is named by its code, written bare:
SLEEP_CHECK, not@sequences/SLEEP_CHECK. The path already says what each segment is. Stored content holds ids; a read with?refs=languagegives the codes back, and a write takes either. - A record is
latest,previousorcurrent.latestis the newest saved,previousthe one before it, andcurrentwhat is in hand: the answers of the sequence being filled in, before it is saved.currentexists only while a sequence is answered. - What is assigned to the person is named in the path too, by code:
tags.ENROLLED,groups.WALKERS,relationships.KEY_WORKER,cohorts.SPRING, tested withexistsin a condition ("assignment exists") andcreatedordeletedin a When ("assignment created"):{ "source": "tags.ENROLLED", "operator": "exists" }. Conditions read these; a text never shows them. - Content names only its own tenant's things and the shared library's.
Where each path can be used
What can be read depends on where it is written. A sequence being answered has this time's answers in hand; a journey doesn't; a title is shown before anything is answered; a text, an email or a push leaves the app, so it names nothing about the person: only the time.
| Place | Where it is written |
|---|---|
| Answering | A sequence's page and atom conditions, and every text of its atoms |
| Journey | A transition's If, an action's condition, and a chat message a node sends |
| Titles | Sequence, page, surface and journey names and descriptions, start and next labels |
| Texts, emails, pushes | A node's createUserSmsMessage, createUserEmailMessage and createUserPushNotification |
| Path | Holds | Answering | Journey | Titles | Texts, emails, pushes |
|---|---|---|---|---|---|
sequenceResponses.<sequence>.latest.scores.<score> | a number | ✓ | ✓ | ✓ | |
sequenceResponses.<sequence>.latest.bands.<score> | text | ✓ | ✓ | ✓ | |
sequenceResponses.<sequence>.latest.atomResponses.<atom> | an answer | ✓ | ✓ | ✓ | |
sequenceResponses.<sequence>.latest.savedAt | a date and time | ✓ | ✓ | ✓ | |
sequenceResponses.<sequence>.previous.scores.<score> | a number | ✓ | ✓ | ✓ | |
sequenceResponses.<sequence>.previous.bands.<score> | text | ✓ | ✓ | ✓ | |
sequenceResponses.<sequence>.previous.atomResponses.<atom> | an answer | ✓ | ✓ | ✓ | |
sequenceResponses.<sequence>.previous.savedAt | a date and time | ✓ | ✓ | ✓ | |
sequenceResponses.current.atomResponses.<atom> | an answer | ✓ | |||
sequenceResponses.current.scores.<score> | a number | ✓ | |||
sequenceResponses.current.bands.<score> | text | ✓ | |||
atomResponses.<atom>.latest | an answer | ✓ | ✓ | ✓ | |
atomResponses.<atom>.previous | an answer | ✓ | ✓ | ✓ | |
atomResponses.<atom>.current | an answer | ✓ | |||
extras.<extra>.latest | text | ✓ | ✓ | ✓ | |
signals.<type>.<measure> | a number | ✓ | ✓ | ✓ | |
time.inNode | a number | ✓ | |||
time.inJourney | a number | ✓ | |||
journeys.<journey>.latest.status | text | ✓ | ✓ | ✓ | |
journeys.<journey>.latest.startedAt | a date and time | ✓ | ✓ | ✓ | |
journeys.<journey>.latest.completedAt | a date and time | ✓ | ✓ | ✓ | |
journeys.<journey>.latest.archivedAt | a date and time | ✓ | ✓ | ✓ | |
journeys.<journey>.latest.pausedAt | a date and time | ✓ | ✓ | ✓ | |
sequences.<sequence>.latest.status | text | ✓ | ✓ | ✓ | |
sequences.<sequence>.latest.answered | yes or no | ✓ | ✓ | ✓ | |
sequences.<sequence>.latest.assignedAt | a date and time | ✓ | ✓ | ✓ | |
sequences.<sequence>.latest.dueAt | a date and time | ✓ | ✓ | ✓ | |
sequences.<sequence>.latest.completedAt | a date and time | ✓ | ✓ | ✓ | |
sequences.<sequence>.latest.archivedAt | a date and time | ✓ | ✓ | ✓ | |
sequences.<sequence>.latest.pausedAt | a date and time | ✓ | ✓ | ✓ | |
tags.<tag> | assigned | ✓ | ✓ | ||
groups.<group> | assigned | ✓ | ✓ | ||
relationships.<relationship> | assigned | ✓ | ✓ | ||
cohorts.<cohort> | assigned | ✓ | ✓ | ||
sequenceResponses.<sequence> | event | ✓ | |||
atomResponses.<atom> | event | ✓ | |||
journeys.current | event | ✓ | |||
user.firstName | text | ✓ | ✓ | ✓ | |
user.age | a number | ✓ | ✓ | ✓ | |
user.cohort | text | ✓ | ✓ | ✓ | |
user.cohort.name | text | ✓ | ✓ | ✓ | |
time.sinceActive | a number | ✓ | ✓ | ✓ | |
time.now.localHour | a number | ✓ | ✓ | ✓ | ✓ |
time.now.weekday | text | ✓ | ✓ | ✓ | ✓ |
time.now.date | a date and time | ✓ | ✓ | ✓ | ✓ |
- A signal's measure is
latest(the newest reading of its type, however old), or for steps, sleep and HRV the day comparison:today,median7d,todayMinusMedian7d,todayOverMedian7d. A new participant has no readings, so a signal clause is false for them until their phone has sent some; give any branch on a measure a way through that always fires. Comparisons against a participant's own baseline aren't computed yet and are refused. - Every date and time also has
.daysAgoand.daysUntil, counted in the participant's local days:extras.QUIT_DATE.latest.daysUntil. timeis a domain of its own:time.nowis this moment (itslocalHour,weekdayanddate, in the participant's time zone), and in a journeytime.inNodeandtime.inJourneyare the seconds since the run arrived at its node and since it started.user.ageis in whole years; the date of birth itself is never offered.- What content, media, embeds and buttons record is read like any answer: a content atom, a
picture, a video or an embed is seen once at least half of it has been on screen for a second;
a video or audio file, or a YouTube or Vimeo embed, also has
playedPercent(the share of it actually played, each second counted once, so skipping ahead doesn't count) andplayedSecs(the time spent playing, rewatches included); a button is pressed, and haspressCount. In a When, the same atom is seen or is pressed each time it records something new:atomResponses.WELCOME_VIDEO.latest.playedPercentat least 80. - A field inside JSON nobody declared, a JSON extra or a KinesteX workout's summary, is named
by its path inside the value, with the type you give it (number, text, yes or no, or a date read
through its days):
extras.PROFILE.latest.address.city. In the condition editor, step into the value and choose A field inside…. A field the value doesn't hold is absent, so is missing holds for it.
Checked where it is written
The server checks every path and token by its place when content is saved. A sequence or a journey
saves with its problems listed and can't be published until they are gone; an atom, published
on save, is refused. GET /api/v1/references?place= (getReferences) lists what can be named in a
place, with each item's code and name: it is what the portal's search offers.
A path that passes the check can still find nothing for a participant — a sequence they haven't answered, an extra never set. That is not an error: a clause on it is false, and a token shows its fallback.
Tokens in text
{{ path }} puts a value into text; {{ path | fallback: "words" }} says what to show when there
is nothing there.
Last time you slept {{ atomResponses.SLEEP_HOURS.latest }}.
Hi {{ user.firstName | fallback: "there" }}
- A value reads as a person reads it. An answer shows the option's label, a number its unit,
a date and time in the participant's time zone ("Tue 4 Nov, 23:10"),
time.now.weekdaythe day's name. An answer's.valuegives the stored value instead. - A
single_selectwhose option values are atom codes quotes the picked atom's answer with.response. - Nothing there shows the fallback, or nothing. A token that isn't a path is a problem at save, like a misspelt path.
- Each value is escaped for the field it lands in: a rich-text label as HTML, a content body as Markdown, everything else as plain text. A participant's words never become markup.
- The app fills sequence, page, surface and journey titles and every atom text; the server fills a chat message, text, email or push a node sends, when it is sent.