Tags

new

Labels for events, states, and context

Behaviour TrackingEngagementCohort AnalysisPersonalization

Introduction

Biomarkers and scores capture what the body does. Tags capture everything else—a symptom, a medication, a shift, a subscription—as discrete, time-bound records ready to query, stream, and correlate with health signals.

Tags can come from anywhere—the Sahha SDK reading device health platforms, Sahha's platform integrations with Garmin, Oura, and other connected services, or direct API calls from your own app or backend. Whatever the source, every tag arrives in the same shape: a name, an optional value, a type, and a time range.

Related products

Need numeric metrics like steps, sleep duration, or heart rate? See Biomarkers . Need raw sensor samples? See Data Logs . Need long-term behavioural labels? See Archetypes .


Key Features

Auto-Collected

Reserved tags arrive automatically from the Sahha SDK (reading HealthKit and Health Connect) and from server-to-server integrations with Garmin, Oura, and other connected platforms—all normalized to the same names so your code never branches on source

Events and States

One schema models both moments in time (a dose, a test result) and windows of time (a shift, a subscription)—including open-ended states that stay active until you close them

Analytics-Ready

A strict category > name > value taxonomy with value as a top-level indexed field, so dose-response and severity queries work without parsing free-text notes

Idempotent

Identity is derived from name, type, source, and start time—re-upload the same tuple to update or close a tag, with no risk of duplicates from retries or follow-ups

Custom + Reserved

Send any custom category, name, and value alongside Sahha's reserved namespace—one schema, one pipeline, one set of webhooks for both

Real-time Streaming

Every tag streams via webhooks the moment it lands—useful for triggering nudges, segmentation, or downstream analytics in real time


How It Works

A tag is a structured record with a name (what's being tracked), an optional value (the variant, severity, or dose), a type ( event or state), and a time range. The same schema is used whether the tag is a clinical signal collected by the SDK or a business event posted from your backend.

Events are points in time—a moment was recorded and nothing more. endDateTime must be null.

States span windows. A closed state has both startDateTime and endDateTime set. An open state has endDateTime: null, meaning the window is still active; close it later by re-uploading the same tag with an endDateTime set.

Identity is the tuple, not the id. Each tag's identity is derived from profile + name + type + source + startDateTime. The id field is server-generated; any value supplied on upload is ignored. Re-uploading a tag with the same identity tuple updates the existing record, so retries after transient errors and follow-up edits never create duplicates.

Tags flow into Sahha through three channels: the mobile SDK, Sahha's platform integrations with services like Garmin and Oura, and direct API calls from your backend or app. Reserved names must follow the schema below regardless of channel; custom names can use any shape. Reserved tags with a non-matching shape are rejected per-item (200 OK with the rejection reported in warnings)—the rest of the batch still lands. Tags flow out via the API and webhooks in the same shape they came in.


List of Tags (Reserved)

Sahha maintains a curated namespace of tag names with a fixed schema . Reserved tags can come from any source—the Sahha SDK (reading HealthKit and Health Connect), Sahha's platform integrations with Garmin, Oura, and other connected services, or your own app or backend—as long as the tag's type, category, and value match the entry below. Names inside this namespace arrive in a consistent shape across platforms and may power native analytics in future releases.

The namespace will grow over time. Currently it covers two categories: symptom and reproductive.

Symptom

Every symptom tag is logged with the same value set—a five-level severity scale—and arrives as a point-in-time event.

Name Type Possible Values
abdominal_cramps event unknown, not_present, mild, moderate, severe
acne event unknown, not_present, mild, moderate, severe
appetite_changes event unknown, not_present, mild, moderate, severe
bladder_incontinence event unknown, not_present, mild, moderate, severe
bloating event unknown, not_present, mild, moderate, severe
breast_pain event unknown, not_present, mild, moderate, severe
chills event unknown, not_present, mild, moderate, severe
constipation event unknown, not_present, mild, moderate, severe
coughing event unknown, not_present, mild, moderate, severe
diarrhea event unknown, not_present, mild, moderate, severe
dizziness event unknown, not_present, mild, moderate, severe
dry_skin event unknown, not_present, mild, moderate, severe
fainting event unknown, not_present, mild, moderate, severe
fatigue event unknown, not_present, mild, moderate, severe
fever event unknown, not_present, mild, moderate, severe
generalized_body_ache event unknown, not_present, mild, moderate, severe
hair_loss event unknown, not_present, mild, moderate, severe
headache event unknown, not_present, mild, moderate, severe
heartburn event unknown, not_present, mild, moderate, severe
hot_flashes event unknown, not_present, mild, moderate, severe
loss_of_smell event unknown, not_present, mild, moderate, severe
loss_of_taste event unknown, not_present, mild, moderate, severe
lower_back_pain event unknown, not_present, mild, moderate, severe
memory_lapse event unknown, not_present, mild, moderate, severe
mood_changes event unknown, not_present, mild, moderate, severe
nausea event unknown, not_present, mild, moderate, severe
night_sweats event unknown, not_present, mild, moderate, severe
pelvic_pain event unknown, not_present, mild, moderate, severe
rapid_pounding_or_fluttering_heartbeat event unknown, not_present, mild, moderate, severe
runny_nose event unknown, not_present, mild, moderate, severe
shortness_of_breath event unknown, not_present, mild, moderate, severe
sinus_congestion event unknown, not_present, mild, moderate, severe
skipped_heartbeat event unknown, not_present, mild, moderate, severe
sleep_changes event unknown, not_present, mild, moderate, severe
sore_throat event unknown, not_present, mild, moderate, severe
vaginal_dryness event unknown, not_present, mild, moderate, severe
vomiting event unknown, not_present, mild, moderate, severe
wheezing event unknown, not_present, mild, moderate, severe

Reproductive

Reproductive tags use a mix of event and state types, each with its own closed value set (or no value for presence-only flags).

Name Type Possible Values
menstrual_flow event unknown, not_present, light, medium, heavy
intermenstrual_bleeding event
infrequent_menstrual_cycles event
irregular_menstrual_cycles event
persistent_intermenstrual_bleeding event
prolonged_menstrual_periods event
pregnancy state
lactation state
ovulation_test event inconclusive, negative, high, positive
cervical_mucus event unknown, dry, sticky, creamy, watery, egg_white, unusual
sexual_activity event unknown, protected, unprotected
contraceptive state unknown, implant, injection, intravaginal_ring, iud, oral, patch
pregnancy_test event inconclusive, negative, positive
progesterone_test event inconclusive, negative, positive

Reserved Additional Properties

The additionalProperties field is free-form on every tag—use any keys you need on custom tags. The keys below are populated by the SDK on specific reserved tags, so custom code shouldn't redefine them on the tags they appear on.

Key Emitted on Values
cycle_start menstrual_flow "true" or "false"

Custom Tags

Outside the reserved namespace there is no fixed schema —pick any category, name, and value your product needs. Custom tags use the same record shape, the same API, and the same webhook stream as reserved ones; they're stored and returned exactly as sent.

Typical custom use cases:

  • Workforce & shifts night_shift, on_call, field_deployment
  • Subscription & lifecycle premium_tier, trial, lapsed, onboarding_complete
  • Clinical & coaching medication, therapy_session, intervention_arm
  • Habits & lifestyle caffeine, alcohol, meal, meditation

Built-in analytics interpret reserved names only. Custom tags are first-class for storage and delivery, and any reporting you build on top can mix both freely.


Use Cases

Symptom & Wellness Journaling

Let users log how they feel against the body's signals—correlate flares with sleep, activity, or stress

Clinical & Reproductive Health

Capture medications, treatments, and reproductive events with timestamped accuracy, ready for study analysis

Workforce & Operations

Tag shifts, on-call windows, or deployment periods to see how operating conditions affect recovery and readiness

Subscription & Cohort Analysis

Track plan tiers, trial status, or program enrolment as ongoing states for segmentation and retention insights

Feature Impact

Tag exposure to product features as states and measure their effect on health outcomes over time

Coaching & Intervention

Trigger nudges based on what the user is doing right now—not just what the body is doing


Output Schema

Every tag returns a consistent JSON structure regardless of source or category.

id UUID

Server-generated. Ignored on upload—identity is derived from name, type, source, and startDateTime under the profile

type string

event (point in time) or state (window of time)

category string nullable

Optional bucket—reserved (symptom, reproductive) or any custom value

name string

The primary signal—reserved name or custom

value string nullable

Optional severity, dose, or variant. May be null for presence-only flags

source string

Which system produced the tag. SDK and integrations set this automatically; backend uploads typically use a reverse-DNS string (e.g. acme.workforce)

startDateTime datetime

When the tag began (ISO 8601)

endDateTime datetime nullable

When the tag ended. Must be null for events; may be null for open states

receivedAtUtc datetime

UTC timestamp when Sahha received the tag

additionalProperties object<string,string> nullable

Auxiliary metadata. Sahha uses reserved keys only; custom keys are stored and returned verbatim

Example: closed state
json
{
"id": "f3b1a8e0-4c5d-4e6f-9a8b-1c2d3e4f5a01",
"type": "state",
"category": "work",
"name": "night_shift",
"value": "12_hour",
"source": "acme.workforce",
"startDateTime": "2024-02-09T19:00:00+00:00",
"endDateTime": "2024-02-10T07:00:00+00:00",
"receivedAtUtc": "2024-02-10T07:05:02+00:00",
"additionalProperties": {
"role": "icu_nurse",
"overtime": "false"
}
}

More examples

An open state —still active, no end yet. Close it later by posting a follow-up tag with the same name and an endDateTime.

Example: open state
json
{
"id": "f3b1a8e0-4c5d-4e6f-9a8b-1c2d3e4f5a02",
"type": "state",
"category": "subscription",
"name": "premium_tier",
"value": "annual",
"source": "acme.billing",
"startDateTime": "2024-01-28T00:00:00+00:00",
"endDateTime": null,
"receivedAtUtc": "2024-01-28T00:00:16+00:00",
"additionalProperties": {
"autoRenew": "true",
"priceUsd": "99"
}
}

An event with a dose type: "event" requires endDateTime: null. The value field carries the dose at the top level so analytics can aggregate without parsing notes.

Example: event with dose
json
{
"id": "f3b1a8e0-4c5d-4e6f-9a8b-1c2d3e4f5a03",
"type": "event",
"category": "medication",
"name": "insulin_bolus",
"value": "6_units",
"source": "acme.clinical",
"startDateTime": "2024-02-11T12:30:00+00:00",
"endDateTime": null,
"receivedAtUtc": "2024-02-11T12:30:10+00:00",
"additionalProperties": {
"timing": "before_breakfast"
}
}

FAQ

When should I use a Tag vs a Biomarker?

Biomarkers are for numeric metrics (steps, heart rate, sleep duration). Tags are for categorical signals—anything described by a name, a variant, or a severity rather than a number. Symptom severity, a positive test result, an active subscription, and a 12-hour shift are all tags.

Can my app or backend write tags that use reserved names?

Yes. Reserved names aren't off-limits to your code—they just have a fixed schema. As long as the tag's type, category, and value match the entry for that name in the reserved list , you can write it from anywhere: the SDK, your app, or your backend. Posts with a different shape are rejected for that tag only and may be excluded from native analytics.

What happens if a reserved tag has the wrong shape?

Only that tag is rejected—other tags in the same batch still land. The upload returns 200 OK with null at the rejected slot in the positional ids array and an entry in warnings explaining why (for example, a value outside the reserved enum). Custom (non-reserved) tags are never rejected by this validator.

Can I create my own custom tags?

Yes. Outside the reserved namespace there's no fixed schema—use any category, name, and value you need. Custom tags are stored and returned exactly as sent and stream via webhooks alongside reserved tags. Sahha's built-in analytics interpret reserved names only, but any reporting you build is free to use both.

How do I close an open state?

Re-upload the same tag with endDateTime set. As long as name, type, source, and startDateTime match the original, Sahha treats it as the same record and updates it in place. To keep the open and closed records as separate timeline entries instead, change one of those identity fields (typically startDateTime) so they form a distinct tuple.

Where can tags come from?

Tags flow in from three places: the Sahha SDK reading device health platforms, Sahha's platform integrations with services like Garmin and Oura, or direct API calls from your own app or backend. Reserved or custom, every tag uses the same shape and pipeline. Use the source field on each tag to record where it came from.

What happens if I post the same tag twice?

Sahha derives each tag's identity from profile + name + type + source + startDateTime, not from the id field (which is server-generated and ignored on upload). Re-uploading the same identity tuple updates the existing record, so retries after transient errors and follow-up edits never create duplicates.


Getting Started


Support

For help with Tags, reach out in the Slack community or contact support@sahha.ai .

Previous
Biomarkers