> ## 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.

# Errors

> SynheartWearError taxonomy, permission failures, and adapter-specific error modes

The wear SDK exposes a small typed error hierarchy. Most adapters emit one of three error subtypes; vendor-specific failures bubble through `originalException`.

## Base type

```dart theme={null}
class SynheartWearError implements Exception {
  final String message;
  final String? code;
  final Exception? originalException;
}
```

Subtypes:

| Class                    | Code                 | Trigger                          |
| ------------------------ | -------------------- | -------------------------------- |
| `PermissionDeniedError`  | `PERMISSION_DENIED`  | OS or adapter permission denied. |
| `DeviceUnavailableError` | `DEVICE_UNAVAILABLE` | No wearable / source available.  |
| `NetworkError`           | `NETWORK_ERROR`      | Cloud adapter HTTP/RPC failure.  |

`originalException` carries the platform-specific underlying error so callers can inspect when the typed code is too coarse.

## Permission denied

Triggered by:

* HealthKit refusing access to a requested `HKObjectType`.
* Health Connect missing a permission grant for `HeartRateRecord` (etc.).
* BLE permission revoked at OS level.
* Vendor OAuth flow cancelled by user or rejected by vendor.
* `requestPermissions` returning `denied` for any requested type.

Recovery:

* For OS permissions: surface a UI rationale and call `requestPermissions` again with the missing types.
* For OAuth: route the user back through the vendor's auth URL.
* For revoked permissions: the SDK reports `ConsentStatus.revoked`, distinct from `denied` (never granted vs previously granted).

## Device unavailable

Triggered when the adapter cannot find a viable data source:

* HealthKit not present (iPad without paired iPhone).
* Health Connect not installed or unsupported on the Android version.
* BLE HRM scan times out without finding a 0x180D device.
* Vendor webhook subscription not active.
* `streamHR` invoked while no underlying source is connected.

Recovery: the SDK retries on `streamHR`/`streamHRV` reconnects, but `readMetrics()` calls fail fast. Hosts should display a "no device connected" UI and let the user reconnect.

## Network error

Triggered by HTTP/RPC failures in the cloud adapters (WHOOP, Garmin, Fitbit). The `originalException` wraps the underlying transport error.

Common causes:

* Vendor token expired (caller should refresh OAuth).
* Vendor rate-limit (HTTP 429); adapters back off.
* Server-side outage (5xx).
* TLS validation failure.

Recovery: per-adapter, with exponential backoff. Token-related failures may surface as `PermissionDeniedError` instead, depending on the vendor.

## Adapter-specific errors

### HealthKit

| Code                                 | Cause                                             |
| ------------------------------------ | ------------------------------------------------- |
| `HKErrorAuthorizationDenied`         | User denied at HealthKit prompt.                  |
| `HKErrorAuthorizationNotDetermined`  | Not asked yet.                                    |
| `HKErrorNoData`                      | Sample query returned empty.                      |
| `HKErrorRequiredAuthorizationDenied` | Required type missing in `HKHealthStore` request. |

The SDK maps these to `PermissionDeniedError` or `DeviceUnavailableError` as appropriate.

### Health Connect

| Code                 | Cause                                       |
| -------------------- | ------------------------------------------- |
| `INSTALL_REQUIRED`   | Health Connect not installed on the device. |
| `MIGRATION_REQUIRED` | User must migrate from older Google Fit.    |
| `NO_PERMISSIONS`     | Permission grant missing.                   |

The SDK surfaces these as `DeviceUnavailableError` (install/migration) or `PermissionDeniedError` (permissions).

### BLE HRM

| Failure                       | Surface                                                                  |
| ----------------------------- | ------------------------------------------------------------------------ |
| Bluetooth permission denied   | `PermissionDeniedError("Bluetooth")`.                                    |
| Adapter off                   | `DeviceUnavailableError("Bluetooth disabled")`.                          |
| Scan timeout (no 0x180D)      | `DeviceUnavailableError("No HRM found")`.                                |
| Connection lost mid-stream    | `streamHR` emits a `NetworkError`-shaped event, then attempts reconnect. |
| Device sends malformed packet | Sample dropped, stream continues.                                        |

### WHOOP (cloud)

| Failure                           | Surface                                                           |
| --------------------------------- | ----------------------------------------------------------------- |
| OAuth token expired               | `PermissionDeniedError`; caller refreshes via `WhoopProvider`.    |
| Webhook subscription invalid      | `DeviceUnavailableError`; caller re-subscribes.                   |
| RAMEN delivery channel closed     | Caller-transparent reconnect; final close emits no error.         |
| `fetchRawDataForFlux` returns 4xx | `NetworkError` with the vendor's response in `originalException`. |

### Garmin (cloud)

| Failure                                                 | Surface                                                    |
| ------------------------------------------------------- | ---------------------------------------------------------- |
| OAuth token expired                                     | `PermissionDeniedError`.                                   |
| Webhook arrived but REST pull (DeliveryHint.ping) fails | `NetworkError`; no `WearMetrics` emitted for this event.   |
| Rate-limited (429)                                      | Adapter backs off; caller sees no error unless persistent. |

### Garmin Health SDK (RTS)

| Failure             | Surface                                                                     |
| ------------------- | --------------------------------------------------------------------------- |
| License missing     | `DeviceUnavailableError("Garmin Health SDK not licensed")` at `initialize`. |
| Pairing failed      | `DeviceUnavailableError`.                                                   |
| RTS connection drop | Auto-reconnect; logged.                                                     |

### Apple Health XML backfill

| Failure                       | Surface                                                              |
| ----------------------------- | -------------------------------------------------------------------- |
| `export.zip` malformed        | `SynheartWearError("Invalid Apple Health export")`.                  |
| XML parse error               | Same; the underlying `XmlParserException` is in `originalException`. |
| Runtime backfill ingest fails | Bubbles up the runtime's negative return code.                       |

## Logging policy

The SDK logs at `info`/`warn` and never logs:

* Vendor OAuth tokens.
* Personal `device_id`s in plain (debug builds may; production ones do not).
* Raw HRV/RR sample bytes.
* XML payloads beyond malformed-record line numbers.

`WearMetrics` snapshots are not logged. Hosts that need observability should collect them at the consumer layer.

## Validation invariants

Regardless of error state:

* `WearMetrics.timestamp` is always UTC ISO-8601.
* Numeric metric values are finite (NaN/inf get filtered to `null`).
* Event ordering is preserved within a single adapter; cross-adapter ordering uses `timestamp`.

## Related

* [Wear Overview](/synheart-wear/overview) — config flags that influence error paths.
* [Adapters](/synheart-wear/adapters) — per-vendor flow.
* [Data Schema](/synheart-wear/data-schema) — null-vs-missing semantics.
