Participant app

The Trialflare participant app — the mobile app and its web version at join.trialflare.com — is built on the endpoints below. They are how a participant, rather than a member of staff, reads their stages, submits answers, exchanges messages with the study team, and connects a wearable. Use them to build your own participant-facing experience: a study-branded app, a kiosk in a clinic, a chatbot that fills in a diary.

These endpoints are separate from Participant management, which is what staff use about participants. The difference is who is authenticated: here it is the participant themself.

Authenticating as a participant

A participant does not have an API key. Instead the app signs in as them and receives a participant token, which is sent as a bearer token exactly like an API key — Authorization: Bearer <token> — on every request below. The token identifies one participant in one trial; it cannot reach any staff endpoint.

There are two ways to get one:

Sign in with the participant's credentials. POST /participants/sessions with the trial code, the participantId, and their password (or the trial's password, where the trial uses one instead) returns {"token": "…"}. If the trial allows self-registration and the participant ID is new, this registers them. If the trial has eConsent on, a first sign-in must also carry the eConsentEmail or eConsentPhone they consented with and the eConsentCode sent to it; without them the request is refused with 400 and details.eConsentRequired: true.

Exchange a sign-in link. Staff can create a one-time sign-in link for a participant with POST /trials/<trialId>/participants/<participantId>/grants (Participant management). The grant= value from that link, posted as {"grant": "…"} to POST /participants/grants/exchange, returns {"token": "…"}. Each grant works once. This is the route to use when your own system is handing a participant over to Trialflare, since it involves no password.

DELETE /participants/sessions signs the participant out, invalidating the token.

Endpoints

Method Path Description
GET /participants/me The signed-in participant, their trial and sites, what they have consented to, and which services are connected.
PUT /participants/me Set the participant's timezone.
GET /participants/trial The trial's name, sponsor, description and team.
GET /participants/trials/<trialId>/stages The participant's stages and progress.
GET /participants/trials/<trialId>/stages/<stageId> One stage, ready to render.
POST /participants/trials/<trialId>/stages/<stageId>/responses Submit answers to a stage.
GET /participants/trials/<trialId>/resources The trial's participant resources.
GET PUT /participants/messages The conversation with the study team; mark it read.
POST /participants/messages Send the study team a message.
GET /participants/events?from=&to= The participant's calendar events.
GET /participants/events/<eventId> One event.
GET /participants/meetings Video meetings booked with the participant.
POST DELETE /participants/connections/<service> Connect or disconnect a wearable or device account.
PUT /participants/me/pushToken Register a device for push notifications.

Stages

GET /participants/trials/<trialId>/stages returns {"stages": [...], "ratioComplete": 0.4}: every stage the participant can see, once per occurrence, in timeline order. Each has its name and description (with occurrence placeholders filled in), enabled (whether it can be opened now), completed and completedAt, enableAt (when it opens), expiresAt where it has an availability window and expired once that has passed, and adHoc for stages that are always available. An occurrence of a recurring stage has an _id of the form <stageId>::<n>; use it as-is in the two endpoints below. ratioComplete is completed timepoints over visible ones.

GET /participants/trials/<trialId>/stages/<stageId> returns the stage with its components in order. A field component's type is the full data type — its format, options, scale, limits — so the client can render and validate it; a built-in component has builtInType (title, sectionDivider) instead. condition on a component says which other field's answer shows it, as described in Stages. Where look-back is on, a field component carries previousResponses, the participant's earlier answers to it, newest first. The stage's postSubmissionText is what to show afterwards.

Submitting answers

POST /participants/trials/<trialId>/stages/<stageId>/responses takes an object keyed by data type _id, each with a value:

{
  "<dataTypeId>": {"value": 7},
  "<dataTypeId>": {"value": ["Nausea", "Fatigue"]},
  "<dataTypeId>": {"value": "2026-09-22T00:00:00Z", "createdAt": "2026-09-21T22:10:00Z"}
}

The value formats are the same as for staff-entered data — see Submitting a response — as are the validations. A required field left blank is refused unless the stage allows partial submission; a stage whose availability window has closed, or a participant who has withdrawn, is refused with 403. The response returned includes postSubmissionText and any onScreenMessages the stage's submission rules produced, for the client to show. Submitting again to the same occurrence amends the earlier answers.

Files, images and videos are uploaded first with GET /uploads/file/request?…&forType=participant (no forId — the token says who), then referenced by fileName — see Uploading files.

Messages

GET /participants/messages returns {"messages": [...]}, oldest first, beginning with the trial's welcome message; each has message, createdAt, who sent it and any fileUrls. PUT marks them read. POST /participants/messages with {"message": "…", "attachments": [...]} (up to 3000 characters; attachments are stored file names from a participant upload) sends one to the study team, who see it in the participant's conversation and, for those subscribed, by email.

Events and meetings

GET /participants/events lists the participant's events between from and to (defaulting to the last 30 days and the next 180) as {"participantEvents": [...]}. GET /participants/meetings lists their booked video meetings as {"meetings": [...]}, each with the join URL to open when it is time.

Connected services

POST /participants/connections/<service> with the code (and scope, or verifier for OAuth 1) from the provider's authorisation flow connects the participant's Garmin, Fitbit or Dexcom account, where the trial has that integration on; DELETE disconnects it. GET /participants/me reports connectedServices. The authorisation flow itself is with the provider, using Trialflare's registered application, so this is mostly of use when reproducing the official app's behaviour.

Push notifications

PUT /participants/me/pushToken with {"pushToken": "…"} registers the device's Firebase Cloud Messaging token so reminders and messages reach it as push notifications. Only the official app's Firebase project is configured, so a third-party app cannot receive Trialflare pushes this way; use the messages endpoint to poll instead.