What this SDK does
Synheart Core connects a Flutter app to the native Synheart Runtime. It:- collects user-consented wearable, phone, and behavior signals;
- sends those signals to the on-device runtime;
- exposes Human State Interface (HSI) 1.3 output as Dart streams;
- optionally stores, synchronizes, and uploads derived state.
HSI is the public output. Most applications should use
Synheart.onStateUpdate for typed values or Synheart.onHSIUpdate for the
canonical JSON. Internal HSV data does not cross the public SDK boundary.Requirements
- Dart
>=3.8.0 <4.0.0 - Flutter
>=3.32.0 - iOS 16 or later
- Android API 28 or later
- A Synheart Runtime binary installed in the host application
synheart_core itself supports iOS 15 and Android API 24, but it depends on
synheart_wear, which needs iOS 16 and API 28. Individual wearable providers
can impose additional OS, hardware, or licensing requirements.
Installation
1. Add the Flutter package
pubspec.yaml:
2. Install the native runtimes
The package alone loads no runtime and produces no HSI. Native artifacts are provisioned by the Synheart CLI, not by pub. Install and authenticate the CLI once:synheart_core 0.23 is verified against runtime 0.46.0 and requires C ABI 4.2
(runtime 0.45.0 or later); older runtimes are refused at initialization. ABI 4.2
changed the daily score inputs, and neither shape parses on the other runtime.
synheart doctor checks the installed runtime against the package.
Commit the generated synheart.lock, which pins each artifact by SHA-256.
Other developers and CI can restore the pinned artifacts with:
- iOS:
synheart/vendor/runtime/ios/SynheartCoreRuntime.xcframework/ - iOS:
synheart/vendor/syni-runtime/SyniRuntime.xcframework/ - Android:
synheart/vendor/runtime/android/jniLibs/<abi>/
3. Configure the platforms
iOS
SetSYNHEART_APP_ROOT near the top of your application’s ios/Podfile:
<app>/synheart/vendor/ during
pod install. Without it they walk up from the pod’s own source directory,
which either fails to find an application root (pods resolved from pub-cache)
or reaches a different project that happens to have a synheart/vendor/
directory — linking a runtime your synheart.lock does not pin.
Merge these settings into the rest of the Podfile:
pod install stops with “transitive dependencies that
include statically linked binaries: onnxruntime.xcframework”. Without the
simulator setting, flutter build ios --simulator fails with
Framework 'SynheartCoreRuntime' not found. Running on an Apple silicon
simulator with flutter run is not affected.
Enable the HealthKit capability when using health data. Add a usage description
to ios/Runner/Info.plist:
synheart_core
0.23 and later ask Apple Health and Health Connect for read access only, so
NSHealthUpdateUsageDescription and the Health Connect WRITE_* permissions
are needed only if your app writes health data itself.
Android
Declare only the permissions required by the sources you enable:synheart_behavior 0.4.1 (required by synheart_core) merges its
notification listener service and the BIND_NOTIFICATION_LISTENER_SERVICE and
READ_PHONE_STATE permissions into your manifest automatically, so you no longer
declare them. If your app does not collect notification or call interruptions,
remove them:
android:allowBackup="false" and android:dataExtractionRules on the
<application>. On iOS the SDK excludes its data directories from backup
itself.
Health Connect requires package queries, a permission-rationale intent, and a
privacy-policy activity alias. Copy the applicable declarations from the
Flutter example manifest.
On Android 14 and later, use FlutterFragmentActivity:
compileSdk 36. Before 0.24.0 the
synheart_wear plugin compiled against 34 and the build failed at
:synheart_wear:checkDebugAarMetadata unless a subprojects block raised it;
with 0.24.0 (synheart_wear 0.7.0) that block is no longer needed.
Start here
The minimum integration has five steps:- Initialize the SDK.
- Ask the user for consent.
- Subscribe to HSI output.
- Start a collection session.
- Stop the session and release resources.
Initialization
SynheartConfig.validate() requires:
- a non-empty
appId; - a non-empty
subjectId; - a
subjectIdthat does not contain|; PrivacyConfig(allowResearch: true)when using research mode.
userId: to initialize() does not populate config.subjectId. Set
subjectId on the config itself.
initialize() throws SynheartRuntimeException (kind: incompatible) when the
installed runtime cannot be used with this SDK: a C ABI other than 4.2 or a
later 4.x, a runtime older than 0.45.0, or a library that does not report its
build info. Its installCommand is the exact command that fixes it. A
missing, wrong-architecture, or unloadable runtime does not throw: the SDK
starts without HSI and Synheart.runtimeError says why.
startSession() after
consent is ready. Use autoStart: true only when consent has already been
completed.
When the health permission prompt appears
WearConfig.autoStartPlatformHealth defaults to true: the HealthKit / Health
Connect reader starts as soon as biosignals consent is granted, and the system
permission prompt can appear at that moment — before your onboarding has
explained it, and again on every launch if you re-grant a remembered consent.
To ask only when the user chooses, turn it off and start the reader yourself:
Synheart.pushWearHr,
from a watch relay or BLE strap) and uses platform health only when the user
picks it.
Modes
When the signed-in identity changes, keep the runtime identity aligned without
reinitializing:
Running without cloud credentials
The SDK is usable with no platform account. OmitCloudConfig and
DeviceAuthConfig, and everything on-device works: collection, HSI
computation, consent, local storage, and baselines. Nothing leaves the device
and no attestation is attempted.
The runtime consent-form flow needs a ConsentConfig to stamp submissions:
Consent
Consent and capability are different:- Capability says what the application is authorized to use.
- Consent says what the user allows.
biosignalsphoneContextbehaviorcloudUploadvendorSyncresearchsyni
phone_context.device_motion
and behavior.app_context. By default a granted category turns on all of its
channels. Set SynheartConfig(strictChannelConsent: true) to enforce consent
per channel instead: the runtime then accepts a sample only when its own channel
is granted (and, when cloud consent is configured, present in the verified
consent token), and the SDK checks the same channel before starting a sensor.
Available since synheart_core 0.18.0.
When a newer version of your app’s published HSM Schema declares an input the
person was never asked about, the runtime keeps it off until they consent.
Synheart.schemaNeedsConsent() is true while that is the case, and
hsmSchemaConsent() returns the schema version and the new channels. Show the
consent form again when it is true. A channel the person declined is not asked
about again. Available since synheart_core 0.21.0 (runtime 0.43.0 or later).
HSI windows reach onHSIUpdate, onStateUpdate and getSessionHsiWindows()
when any collection category (biosignals, behavior, phoneContext) or
research is granted, so an app that collects only behavior still receives its
digital axes. With none of them granted, windows are dropped. Before 0.23.0 a
window needed biosignals or research.
Local development consent
hasConsent() reports whether a channel is currently enforceable, not what the
user chose. Once cloud is configured, the runtime denies every channel until a
consent token is issued.
For hosted consent profiles and JWT lifecycle, see
Consent System.
Feature activation and sessions
Module config enables the corresponding module. Features may also be activated explicitly:
Start and stop the full collection lifecycle:
startSession() requires both halves of a pair: a collection feature enabled
in SynheartConfig (wearConfig / behaviorConfig / phoneConfig) and
its matching consent granted (biosignals / behavior / phoneContext).
Neither alone is enough, and cloudUpload, vendorSync, research, and
syni never qualify — they govern what happens to data once gathered. A
session that could collect nothing throws a StateError instead of starting.Reading HSI
Typed state
Most Flutter applications should use:HSIAxes.hasDigital to check whether the window carried
any of them.
onStateUpdate parses each HSI window once and shares the result across
subscribers. Synheart.currentHSIState reuses the same parse, so it is cheap
to read repeatedly.
Canonical JSON
Use the raw stream when forwarding or validating the exact HSI 1.3 envelope:HSIState.rawJson preserves the same JSON. See
HSI in Synheart Core for mapping details.
Raw source streams
Synheart.getSessionHsiWindows() and Synheart.getSessionWearSamples(). Both
are capped ring buffers — see maxSessionHsiWindows and
maxSessionWearSamples.
Personal baselines and daily scores
The runtime keeps the personal baselines on the device and saves and restores them itself. Most apps need no code for this. Push a daily value (for example from a wearable’s nightly summary), then refresh the reference once. Both calls build the engine pipeline when no session is running (since 0.23.0), so a background health pull or vendor sync is stored:attachStrainScore() before local midnight, or
attachClosedDayStrain() after it.
Score a night against the runtime’s baselines. After
srmRefreshWearableReference(), read Synheart.dailySleepBaselines() for the
Sleep Score and Synheart.dailyBaselineRefs() for Recovery and Readiness:
SleepScoreInputtakestonightMidpointH(the night’s local sleep midpoint, or null without a start and end time) andbaselines. Without the baselines the score stays in its cold-start mode.RecoveryScoreInput.baselinescomes fromdailyBaselineRefs().ReadinessScoreInput.baselineMaturity(0..1) isBaselineRefs.maturity, or0whendailyBaselineRefs()returns null.
cardiac.overnight_hr_bpm
(DailyMetric.overnightHrBpm) is the mean HR asleep, which the Sleep Score
reads. cardiac.resting_hr_bpm is the resting-HR record, which Recovery and
Readiness read. A source with one overnight figure fills both. A daytime resting
HR on its own fills resting only. Push steps and motion.active_min only for
days that are over. Never push cardiac.rest_rmssd_ms; the engine writes it.
Upgrading from 0.19 or earlier?
rollDay() and the SRM/longitudinal snapshot
calls are gone, and daily values use the new DailyMetric names. The first
launch after the upgrade starts the baselines fresh once. From 0.21 or earlier,
the score inputs changed with runtime 0.45 (see Upgrading). See the
changelog for each change.Production device authentication
Production applications should configure hardware-backed device identity:initialize().
Configuring DeviceAuthConfig only makes it possible. Device registration is
deferred until a cloud-bound action needs it:
Synheart.reattestDeviceAuth() as an explicit repair action if the server
has lost or revoked an otherwise valid local device identity. It refreshes the
attestation without rotating the device identity. (reregisterDeviceAuth() is
deprecated and calls it.)
Endpoints
The platform origin ishttps://api.synheart.ai, but the SDK does not bake
it in. SYNHEART_BASE_URL is empty unless you set it, and with nothing
configured the native runtime applies its own default. Set it explicitly on any
build that talks to the cloud:
SYNHEART_AUTH_BASE_URLSYNHEART_CONSENT_BASE_URLSYNHEART_INGEST_BASE_URL
Cloud upload
Cloud upload requires:CloudConfig;DeviceAuthConfigin production;cloudUploadconsent;- an issued consent token and a registered device.
CloudConfig.uploadInterval. No host
code is required to move data.
Inspect the queue and user-facing status:
cloudSyncStatus for user interface copy. Queue depth is more useful for
diagnostics than for end users. If HSI is produced but nothing uploads, the
cause is almost always a closed consent gate — lastUploadError names it.
See Cloud Protocol for wire-level details.
Syni chat and sessions
Runtime 0.21.0 and later expose device-signed, non-streaming Syni service calls. ConfigureSYNHEART_BASE_URL and register the device before calling
them. The SDK keeps the active session id after each successful turn, so
subsequent calls continue the same conversation until startNewSession().
SyniServiceException with deliveryUnknown == true,
inspect the session messages before offering a retry; resending immediately
could duplicate a turn.
On an older native runtime isAvailable is false and the rest of the SDK
continues to work. On-device model installation and streaming chat are covered
in Syni for Flutter.
Errors and diagnostics
Configuration validation throwsSynheartError with a stable code. An
incompatible runtime throws SynheartRuntimeException from initialize() (see
Initialization). Lifecycle preconditions commonly throw
StateError; invalid arguments throw ArgumentError; structured runtime
failures throw SynheartNativeException (sync calls throw its subclass
SyncNativeException).
null, false, or an error code),
Synheart.lastNativeError explains it: a SynheartNativeError with code,
message, retryable, ownerKind, recoveryKind, recoveryCall, reason,
argument, hint, op, and errorId. A call that succeeds clears it.
CoreRuntimeBridge.lastCreateError says why the runtime handle could not be
created.
missingSymbols lists optional native symbols the loaded runtime does not
export. Empty is the healthy state. Anything in it means the vendored runtime
predates this SDK release, so the features behind those symbols are disabled
and return null, -1, or an empty list rather than throwing. The list fills
lazily, so check it after exercising the features you depend on, or pass
probeAll: true for a full audit.
If isAvailable is false, runtimeError and its install command say why. If
symbols are missing, install the runtime the SDK recommends and check it:
synheart/vendor/runtime/.
Threading
Network-touching calls —syncNow(), flushUploads(),
fetchCloudHsiWindows(), requestDataDeletion(), device registration, and the
consent mutations — run on a background isolate and return a Future. The
per-sample push calls (pushWearHr, pushRr, pushAccel, tick,
ingestBatch) are synchronous and safe on the UI isolate at sensor rates.
Diagnostic getters are cheap synchronous FFI reads, but they are still FFI
calls: do not poll them per frame.
Testing
Flutter example application
Initialization, consent, sessions, HSI, watch integration, and runtime diagnostics
Upgrading
Notable changes since 0.10:
The bundle-secret configuration fields —
SynheartConfig.capabilityToken and
.capabilitySecret, CloudConfig.apiKey, and ConsentConfig.appApiKey — are
deprecated and pending removal. The native runtime dropped bundle-secret
configuration as a security fix, so these values are no longer forwarded to it.
Use deviceAuthConfig instead, and never ship an API key or signing secret
inside an application bundle.
See the changelog
for the full list.
Advanced APIs
The package also provides:- local session storage and retention;
- cross-device sync and baseline transfer;
- sleep, recovery, readiness, resilience, and breathing models;
- Apple Health and Health Connect backfill;
- watch-to-phone
EdgeIngest; - lab protocols and research-study enrolment;
- secondary
SynheartInstanceruntime handles; - Syni adaptive AI integration.
Architecture
Runtime, modules, and data flow
Consent System
Consent channels and JWT lifecycle
Scoring Models
Sleep, recovery, and readiness
Health Backfill
Historical HealthKit and Health Connect import