Events and triggers
Every run starts with a trigger — something outside the loop telling the platform to call your run(). The platform packages the trigger’s input into an event and passes it as the first argument:
async run(event: HealthEvent, step: HealthStep) {
const payload = event.body;
// …
}This page covers the shape of event, the trigger paths available today, and how to type the body for autocomplete.
HealthEvent
type HealthEvent<T = unknown> = {
body: T;
};Properties
| Property | Type | Description |
|---|---|---|
body | T | The JSON payload from the trigger. undefined if the trigger sent no body. |
The platform doesn’t enforce a payload schema — whatever JSON the trigger sent lands on body unchanged. Parameterize HealthEvent with your own type and the entire body is typed inside run.
Triggers
Two paths today.
UI
From a loop’s overview page, click Run. A dialog accepts an optional JSON body; submitting kicks off the run and you’ll see it appear under Runs. Whatever you typed in the dialog becomes event.body.
UI-triggered runs are tagged with the user who submitted them.
API
Mint a token under Settings → API tokens, then POST to the loop’s trigger URL:
curl -X POST https://api.ollie.health/loop/$ORG_ID/$LOOP_ID \
-H "Authorization: Bearer $LOOPS_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "patientId": "abc", "phoneNumber": "+15551234567" }'The JSON body of the POST becomes event.body. The response returns a runId you can use to find the run later. A token only works for loops in the org that minted it; revoking the token cuts off its access without touching the loops themselves.
API-triggered runs are tagged with the token, not a user.
Access the event
Take event as the first argument to run and read off body:
async run(event: HealthEvent, step: HealthStep) {
const { patientId } = event.body;
// …
}Whatever is on event.body is recorded as the run’s input and shown on the run detail page — you don’t need to log it separately.
Examples
Type the body for autocomplete
Declare an interface at the top of your loop file and parameterize HealthEvent with it:
interface IntakePayload {
patientId: string;
phoneNumber: string;
source: "kiosk" | "web";
}
export class HealthLoop extends Loop {
async run(event: HealthEvent<IntakePayload>, step: HealthStep) {
const { patientId, phoneNumber, source } = event.body;
// all three fields are typed
}
}Handle a missing body
If the trigger sent no JSON body, event.body is undefined. Guard early:
async run(event: HealthEvent<IntakePayload>, step: HealthStep) {
if (!event.body) {
return { error: "no payload" };
}
const { patientId } = event.body;
// …
}Branch on trigger source
If your loop behaves differently for UI runs versus API runs, encode that on the payload itself rather than trying to read trigger metadata inside run:
interface IntakePayload {
patientId: string;
source: "manual" | "automation";
}
async run(event: HealthEvent<IntakePayload>, step: HealthStep) {
if (event.body.source === "manual") {
// skip downstream notification for hand-triggered runs
}
}