Skip to main content

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.
The Flutter package coordinates platform integrations. Human-state computation, storage, device authentication, and sync run in the separately installed native runtime.
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

Or add the current release to 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:
From your Flutter application root:
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.
synheart install syni is not optional on iOS. synheart_core depends on the syni package, whose iOS pod links its own vendored framework and fails pod install when it is absent — even if your application never uses Syni.
Commit the generated synheart.lock, which pins each artifact by SHA-256. Other developers and CI can restore the pinned artifacts with:
The CLI installs runtime artifacts under:
  • 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

Set SYNHEART_APP_ROOT near the top of your application’s ios/Podfile:
Treat this as required rather than as a monorepo workaround. The Synheart podspecs symlink their vendored framework from <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:
Without static linkage, 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:
Use permission copy that accurately describes your application. 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:
Keep app data out of Android backups: the runtime’s secure store is sealed with a device-bound key that a restored copy cannot use. Set 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:
Build your application module against 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:
  1. Initialize the SDK.
  2. Ask the user for consent.
  3. Subscribe to HSI output.
  4. Start a collection session.
  5. Stop the session and release resources.
The following example uses unsigned capabilities for local development:
allowUnsignedCapabilities: true bypasses production capability verification. Never enable it in a release build.

Initialization

SynheartConfig.validate() requires:
  • a non-empty appId;
  • a non-empty subjectId;
  • a subjectId that does not contain |;
  • PrivacyConfig(allowResearch: true) when using research mode.
Passing 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.
Initialization is idempotent. Concurrent callers share the in-progress initialization, and calls made after initialization are no-ops. By default, initialization does not collect data. Call 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:
Turn it off too when your app pushes heart rate itself (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. Omit CloudConfig 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:
HSI upload, cross-device sync, research-study enrolment, and server-issued capability tokens are unavailable in this configuration. Consent still persists locally; the runtime is offline-first and reconciles with the cloud profile only when cloud upload is enabled. Consent and capability are different:
  • Capability says what the application is authorized to use.
  • Consent says what the user allows.
Standard collection features are operational only when activation, consent, capability, and an active session all permit them. Syni also has its own model installation and execution lifecycle; see Syni for Flutter. The seven consent channels are:
  • biosignals
  • phoneContext
  • behavior
  • cloudUpload
  • vendorSync
  • research
  • syni
Each category is made of finer channels, such as 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.
Observe consent changes:
Revoke one channel or all 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:
Available features are: 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.
Or control individual collectors:
Module-level methods still enforce consent and capability gates.

Reading HSI

Typed state

Most Flutter applications should use:
The typed state contains:
Axis values can be null when there is not enough input or when an older payload does not contain that reading. Missing means unknown; it does not mean zero. The digital axes come from interaction, so they resolve on hardware with no biosignal source. 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

Raw streams are consent-gated. Always cancel subscriptions with their owning widget, provider, or service. Per-session buffers are also available through 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:
Attach Strain with 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:
  • SleepScoreInput takes tonightMidpointH (the night’s local sleep midpoint, or null without a start and end time) and baselines. Without the baselines the score stays in its cold-start mode.
  • RecoveryScoreInput.baselines comes from dailyBaselineRefs().
  • ReadinessScoreInput.baselineMaturity (0..1) is BaselineRefs.maturity, or 0 when dailyBaselineRefs() returns null.
Push heart rate under two names. 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:
The runtime registers a hardware-backed identity and signs supported requests. No shared HMAC secret is required for normal production upload.
DeviceAuthConfig.packageName is the installed bundle id, which Play Integrity and App Attest verify against. It is not SynheartConfig.appId, the platform-issued application identifier. Passing the latter fails attestation.
Registration is triggered by cloud-upload consent, not by initialize(). Configuring DeviceAuthConfig only makes it possible. Device registration is deferred until a cloud-bound action needs it:
Use 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 is https://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:
Optional service-specific overrides:
  • SYNHEART_AUTH_BASE_URL
  • SYNHEART_CONSENT_BASE_URL
  • SYNHEART_INGEST_BASE_URL
Set SYNHEART_BASE_URL rather than relying on a per-service override alone. An override moves one service; the rest keep resolving through SYNHEART_BASE_URL. Overriding auth by itself points device registration at one environment while consent and ingest stay on another, which surfaces as authentication failures with no obvious cause.
The values must be origins. The runtime appends service paths.

Cloud upload

Cloud upload requires:
  1. CloudConfig;
  2. DeviceAuthConfig in production;
  3. cloudUpload consent;
  4. an issued consent token and a registered device.
The runtime uploads on its own. It subscribes to the engine’s HSI broadcast, enqueues each closed window, and POSTs on CloudConfig.uploadInterval. No host code is required to move data. Inspect the queue and user-facing status:
Use 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. Configure SYNHEART_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().
Chat calls are serialized because sending is non-idempotent and order matters. If a timeout produces a 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 throws SynheartError 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).
After any native call that failed (returned 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.
Check runtime health:
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:
Then inspect the native linker output and verify the runtime exists under 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

The SDK repository includes a complete example application. It runs without cloud credentials, so it needs no platform account — only the installed native artifacts:

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 SynheartInstance runtime handles;
  • Syni adaptive AI integration.
Start with the lifecycle above. Integrate advanced APIs only when your product needs them:

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

Resources