Skip to main content

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 session SDK carries two error surfaces: in-stream SessionError events that close a running stream, and synchronous exception classes thrown for API misuse before/during a Stream<SessionEvent> is established. They are aligned across Dart, Kotlin, and Swift.

SessionErrorCode (in-stream)

Wire enum from session.proto:
enum ErrorCode {
  ERROR_UNSPECIFIED         = 0;
  ERROR_PERMISSION_DENIED   = 1;
  ERROR_SENSOR_UNAVAILABLE  = 2;
  ERROR_LOW_BATTERY         = 3;
  ERROR_OS_TERMINATED       = 4;
  ERROR_INVALID_STATE       = 5;
}
SDK projection — wire string is the snake_case JSON value the platform layer ships:
Dart SessionErrorCodeWire stringCause
permissionDeniedpermission_deniedOS denied a sensor permission (HealthKit, Health Connect, BLE, etc.).
sensorUnavailablesensor_unavailableNo paired wearable; HR sensor missing or returning no data.
lowBatterylow_batteryWatch battery dropped below the OS threshold for an active workout/exercise session.
osTerminatedos_terminatedOS killed the foreground/exercise session (memory pressure, user backgrounded, etc.).
invalidStateinvalid_stateAPI misuse: StartSession while already active, unknown session_id, etc.
SessionErrorCode.fromString(value) (all three SDKs) parses the wire string. Unknown values throw ArgumentError in Dart.

SessionError event

class SessionError extends SessionEvent {
  final SessionErrorCode code;
  final String message;
}
SessionError always closes the stream. After it fires, the same sessionId can be started again with a fresh SessionConfig. message is human-readable and platform-specific. Hosts should log it but should not parse it for control flow — only code is stable.

Recovery paths

CodeRecovery
permission_deniedPrompt for the missing permission via the appropriate platform API; restart the session. The SDK does not retry.
sensor_unavailableReconnect the wearable; verify getWatchStatus() reports paired = true, reachable = true, installed = true. Switch to a different BiosignalProvider if available.
low_batteryUser remediation. The SDK does not throttle or downsample.
os_terminatedResume the session with a new sessionId. The OS-killed session is gone; partial summaries are not recoverable from the SDK side.
invalid_stateProgrammer error. Inspect message and the call site. Common causes: starting two sessions with the same id, calling on a disposed SynheartSession.

Dart synchronous exceptions

For misuse that doesn’t reach the engine, the Dart SDK throws:
class SynheartSessionError implements Exception { ... }
class SessionPermissionDeniedError extends SynheartSessionError { ... }
class SessionSensorUnavailableError extends SynheartSessionError { ... }
class SessionInvalidStateError extends SynheartSessionError { ... }
ExceptionWhen
SessionInvalidStateErrorstartSession after dispose(). startSession with a sessionId already running. Constructor preconditions.
SessionPermissionDeniedErrorPermission probe at construction time (rare; most permission failures surface as SessionError events instead).
SessionSensorUnavailableErrorNo usable provider at construction.
These do not reach the event stream — they’re thrown directly from SynheartSession.startSession(...) and friends.

Distinguishing the two surfaces

SituationSurface
You called startSession with a duplicate idDart SessionInvalidStateError thrown synchronously.
You called startSession after dispose()Dart SessionInvalidStateError thrown synchronously.
OS denied HealthKit at session startSessionError(permissionDenied, ...) event on the stream.
Watch went out of range(no error — channel errors are swallowed; the engine continues with empty buffers)
Watch ran out of battery mid-sessionSessionError(lowBattery, ...) event on the stream.
User backgrounded the app and OS killed the workoutSessionError(osTerminated, ...) event on the stream.

Channel error swallowing

The Watch event channel listener treats channel transport errors as non-fatal — dropped frames during a brief disconnect are preferable to forcing the host to handle a SessionError(osTerminated) for every Bluetooth blip. Apps that need strict reachability semantics must poll getWatchStatus() and stop the session manually on persistent disconnect.

Logging

The SDK does not write to platform log streams beyond assert-guarded print calls. Hosts that need structured logging should subscribe to the Stream<SessionEvent> and log at the call site.