Documentation Index
Fetch the complete documentation index at: https://docs.synheart.ai/llms.txt
Use this file to discover all available pages before exploring further.
The Consent Module manages user permissions and enforces privacy boundaries across all Synheart Core modules. The runtime is the authoritative enforcer; this page describes what you control and how.
Core principles
- Explicit consent — every data-collection or upload operation requires an explicit grant.
- Granular control — module-level booleans plus per-channel flags.
- Revocable — consent can be withdrawn at any time; the runtime stops within the next cycle.
- Cloud-verified — when the consent service is configured, a valid JWT is required, not just a local boolean.
- Enforced — missing consent silently drops samples and uploads at the runtime seam; nothing fakes the data.
Consent types
Synheart Core defines six consent types, mirrored across Dart, Kotlin, and Swift:
| Consent type | Wire string | Module / scope | What it enables |
|---|
biosignals | "biosignals" | Wear | HR, HRV, sleep, motion from wearables, RR intervals. |
phoneContext | "phoneContext" / "phone_context" | Phone | Device motion, screen state, app context (no app names). |
behavior | "behavior" | Behavior | Tap/scroll/swipe/typing timing — no content. |
cloudUpload | "cloudUpload" / "cloud_upload" | Cloud Connector | HSI snapshot upload to the platform. |
vendorSync | "vendorSync" / "vendor_sync" | RAMEN stream | Subscription to third-party vendor events (WHOOP, Garmin webhooks). |
research | "research" | Lab session export | Lab-session JSON upload. Required for research-tier uploads. |
All six default to denied. The user grants each one explicitly through a consent flow.
Granular channels
Each module can be consented at a finer grain via ConsentChannels:
| Group | Channels |
|---|
biosignals | vitals, sleep, cardio_advanced, neuromuscular, wearable_motion |
phone_context | device_motion, device_context, system_state |
behavior | digital_activity, notification_patterns, app_context |
interpretation | focus_estimation, emotion_estimation |
When a consent submission carries explicit channel flags, those win over the module-level boolean. When the channel map is absent or all-false but the module-level flag is true, the module-level boolean wins (sensible default for callers that don’t yet pass per-channel flags).
Consent tiers
ConsentTier describes the maximum processing destination:
| Tier | Wire value | Allowed |
|---|
local | "local" | On-device only — nothing leaves the device. Default. |
cloud | "cloud" | Derived/aggregated data may upload to Synheart Platform. |
research | "research" | Raw data may be exported to a research lab. Implies cloud. |
Consent status
The runtime computes a status from the local snapshot plus the cached cloud token:
| Status | Condition |
|---|
granted | Cached JWT is present and not expired. |
expired | Cached JWT is present and past expiry. |
pending | Local grant exists but no token yet, or consent service is configured and the cycle is in flight. |
denied | No token and consent service is not configured. |
Token refresh threshold is 5 minutes — Synheart.consentNeedsTokenRefresh() returns true when the JWT expires within that window.
Consent flow
1. Initial consent request
When the SDK initializes, the consent module loads any persisted snapshot. If nothing is stored, every type is denied.
await Synheart.initialize(
config: SynheartConfig(
appId: 'com.example.app',
subjectId: 'anon_user_123',
consentConfig: ConsentConfig(
appId: 'com.example.app',
appApiKey: appApiKey,
),
),
);
bool ready = await Synheart.hasConsent('biosignals');
if (!ready) {
// Show your consent UI, then submit:
final result = await Synheart.consentSubmitForm(
deviceId: deviceId,
platform: 'ios',
formJson: jsonEncode(consentForm),
);
// result.token is the issued JWT; consent module stores it automatically.
}
2. Granting
Two paths grant consent:
- Local grant —
Synheart.grantConsent(...) mutates the local snapshot. Useful for unit tests and dev. In production with a configured consent service, the runtime will still report pending until a JWT is issued.
- Cloud submit —
Synheart.consentSubmitForm(...) sends a signed form to the consent service and stores the returned JWT. This is the production path.
3. Storage
The consent module persists state via the platform’s secure storage (Keychain on iOS, EncryptedSharedPreferences on Android, keyring on desktop). The runtime registers its own secure-storage callbacks during init; you don’t manage this directly.
What is stored:
- The local
ConsentSnapshot (per-type booleans, channels, tier, timestamps).
- The cached
ConsentToken (JWT, profile id, expiry, scopes).
- Per-platform storage keys are scoped per
subject_id.
What is not stored:
- Private keys (those live in the Secure Enclave / Keystore via
synheart-auth).
- Raw form input fields beyond the consent submission.
4. Revocation
await Synheart.revokeConsentType('biosignals'); // single type
await Synheart.revokeConsent(); // all types
Effects:
- Each module checks consent before pushing samples; revoked modules’ samples are silently dropped at the runtime engine seam.
- Cloud uploads stop on the next flush — the runtime’s connector hook reads the consent state before draining the queue.
- Local data is not deleted automatically. Use
Synheart.wipeLocalData() if you need a hard wipe.
Enforcement
hasConsent semantics
hasConsent(type) =
if cloudConsentConfigured AND consentStatus != granted:
return false
return localSnapshot.allows(type)
When the consent service is configured (typical in production), the local snapshot is not authoritative on its own — a valid cloud-issued JWT must be present.
Per-action gates
| Action | Required consent type(s) |
|---|
| Push HR / RR / vendor HRV / sleep stages | biosignals |
| Push behavior events | behavior |
| Push phone context | phoneContext |
| HSI cloud upload | cloudUpload |
| RAMEN stream subscription | cloudUpload AND vendorSync |
| Lab session export | research |
The runtime applies these gates at the engine seam. Sample pushes that fail a gate are silently dropped.
Account-deletion override
Synheart.requestAccountDeletion() puts the runtime in a wind-down state that overrides every consent type to denied for outbound traffic, regardless of stored state. cancelAccountDeletion() lifts the override.
Cold-start consent buffer
The runtime opens an HSI session immediately, but the consent token may take seconds to fetch on a fresh app launch. To avoid losing the first window:
- The auto-enqueue bridge buffers up to 8 pending HSI windows when consent is
pending.
- On
granted, the buffer drains and replays into the upload queue.
- If consent never lands, the oldest buffered windows drop FIFO.
This is automatic and not configurable from the SDK.
API reference
Querying state
// Snapshot summary (booleans + timestamp).
final snapshot = Synheart.shared.currentConsent;
// Coarse status enum.
final status = Synheart.getConsentStatus();
// Returns: ConsentStatus.granted | .pending | .denied | .expired
// Raw status map (status string + last-updated timestamp).
final raw = Synheart.consentStatus();
// True when the JWT expires within 5 minutes.
final needsRefresh = Synheart.consentNeedsTokenRefresh();
// Single-type query.
final ok = await Synheart.hasConsent('biosignals');
Mutating state
// Grant per channel (named-bool form is the canonical signature).
await Synheart.grantConsent(
biosignals: true,
behavior: true,
phoneContext: false,
cloudUpload: false,
);
await Synheart.revokeConsentType('biosignals');
await Synheart.revokeConsent(); // revoke every channel
Cloud submission
// Get the editable form (built from cached profile + current snapshot).
final form = await Synheart.consentGetEditableForm();
// Submit and receive a JWT. Signed by the device key automatically.
final result = await Synheart.consentSubmitForm(
deviceId: deviceId,
platform: 'ios',
userId: userId, // optional
formJson: jsonEncode(updatedForm),
);
// Force a token refresh if needed.
await Synheart.ensureCloudConsentReady();
The consent service URL is read from ConsentConfig.consentServiceUrl (defaults to https://consent.synheart.ai) or your enterprise override.
Privacy invariants
These hold regardless of consent state:
- The SDK never collects content (no text, no clipboard payloads, no notification bodies, no audio).
- Raw
subject_id never leaves the device — only subject_hash.
- Token material is in-memory only; never persisted in plaintext.
- Revocation pauses outbound traffic on the next flush, not on a timer.