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

# Implementing consent

> Wiring consent into your app — Synheart consent vs OS permissions, the order they must happen in, and the platform entries each type requires.

Consent in a Synheart app lives on **two independent layers**. Both must be granted before the runtime ingests data; granting one does not imply the other. This page is the implementation guideline — read [Consent System](/synheart-core/consent-system) for the full reference and API surface.

## The two layers

```mermaid theme={null}
flowchart LR
    U[User] -->|1. In-app UI| SH[Synheart consent<br/>biosignals · phoneContext · behavior<br/>cloudUpload · syni · vendorSync · research]
    U -->|2. OS prompt| OS[Device permission<br/>HealthKit · Health Connect · BLE<br/>Notification listener · Phone state]
    SH --> RT[Runtime ingest]
    OS --> RT
    RT --> APP[HSI / typed state]
```

| Layer                 | Issued by             | Authorises                                                                               | Lives in                                                                                            |
| --------------------- | --------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Synheart consent**  | Your app, via the SDK | Whether the runtime is allowed to *process, fuse, and upload* the data your app can read | `ConsentSnapshot` in secure storage, plus a cloud-issued JWT when the consent service is configured |
| **Device permission** | The operating system  | Whether your app can *read* a sensor or data source at all                               | OS Settings → Privacy                                                                               |

The two layers are independent. `Synheart.hasConsent('biosignals')` returns `true` as soon as the Synheart layer is granted, even if the user denied HealthKit. The data simply never arrives — adapters silently drop on the OS side. This is by design (consent state is portable across reinstalls; OS permission state is not), but it means **you cannot use `hasConsent` to test for OS permission**. Query `SynheartWear.requestPermissions(...)` for that.

## Required order

Always request in this order. The reverse works mechanically but produces worse UX and higher decline rates.

1. **Initialise the SDK.** `Synheart.initialize(...)` with a `ConsentConfig` so the runtime knows whether a cloud consent service is configured.
2. **Synheart consent.** Show your in-app consent UI, then either:
   * `Synheart.grantConsent(...)` — local-only (dev, tests, on-device tier).
   * `Synheart.consentSubmitForm(...)` — production; returns a JWT, stored automatically.
3. **OS permission(s).** Request only the OS permissions corresponding to the consent types the user just granted (see mapping below).
4. **Start the module** (`SynheartWear.startStreaming`, `SynheartBehavior.start`, etc.). The runtime gates every sample on the Synheart layer; adapters gate every sample on the OS layer.

## Synheart consent types

The runtime defines **seven** consent types. All default to denied.

| Type           | What it gates                                            | Wire string     |
| -------------- | -------------------------------------------------------- | --------------- |
| `biosignals`   | HR, HRV, sleep, RR, wearable motion (Wear module)        | `biosignals`    |
| `phoneContext` | Device motion, screen state, system state (Phone module) | `phone_context` |
| `behavior`     | Tap / scroll / swipe / typing timing (Behavior module)   | `behavior`      |
| `cloudUpload`  | HSI snapshot upload to Synheart Platform                 | `cloud_upload`  |
| `syni`         | On-device LLM input (persona, conversation state)        | `syni`          |
| `vendorSync`   | RAMEN subscription to third-party vendor events          | `vendor_sync`   |
| `research`     | Lab-session JSON export                                  | `research`      |

## Mapping: Synheart consent → device permissions

This is the canonical mapping, verified against the iOS, Android, and Flutter SDKs.

### `biosignals`

**iOS — HealthKit + BLE.** Add to `Info.plist`:

```xml theme={null}
<key>NSHealthShareUsageDescription</key>
<string>Synheart reads heart-rate, HRV, and sleep data to compute your HSI.</string>
<key>NSHealthUpdateUsageDescription</key>
<string>Synheart writes derived metrics back to Health.</string>
<!-- Only if you use BLE HRM or the Garmin BLE companion -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Synheart connects to your BLE heart-rate monitor.</string>
```

Then call `SynheartWear.requestPermissions(...)` for the `PermissionType` values you need (`heartRate`, `hrv`, `sleep`, `steps`, `calories`, `distance`).

**Android — Health Connect + BLE.** Add to `AndroidManifest.xml`:

```xml theme={null}
<uses-permission android:name="android.permission.health.READ_HEART_RATE"/>
<uses-permission android:name="android.permission.health.READ_HEART_RATE_VARIABILITY"/>
<uses-permission android:name="android.permission.health.READ_STEPS"/>
<uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED"/>
<uses-permission android:name="android.permission.health.READ_DISTANCE"/>
<uses-permission android:name="android.permission.health.READ_HEALTH_DATA_HISTORY"/>
<!-- WRITE_* equivalents only if you also write back -->

<!-- Android 12+ BLE HRM only -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

<queries>
  <package android:name="com.google.android.apps.healthdata"/>
  <intent>
    <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE"/>
  </intent>
</queries>
```

You also need the Health Connect rationale intent-filter on your launcher activity and a `ViewPermissionUsageActivity` alias — see `synheart-wear-flutter`'s README for the full activity block.

**Vendor cloud sources** (WHOOP, Garmin Cloud, Fitbit): OAuth flow only — no OS permission.

### `phoneContext`

The Phone module collects raw accelerometer/gyroscope via `CMMotionManager` (iOS) and `SensorManager` (Android), plus screen-state and system-state via OS callbacks.

* **iOS:** no `Info.plist` entry required. Raw `CMMotionManager` is permission-free on iOS — `NSMotionUsageDescription` is only needed for `CMMotionActivityManager` / `CMPedometer`, which Synheart does not use.
* **Android:** no runtime permission required. Raw `SensorManager` sensors are permission-free. (Only `ACTIVITY_RECOGNITION` would be needed for the step-counter sensor, which Synheart does not use directly — step counts come from Health Connect under `biosignals`.)

This is the one consent type where Synheart consent alone is enough.

### `behavior`

Touch/scroll/typing timing is captured via view-tree hooks inside your app — no OS permission needed. **Notification and call-state signals are separate** and do require OS permission on each platform.

**iOS:**

```swift theme={null}
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { _, _ in }
```

No `Info.plist` entry; iOS surfaces the prompt via `UNUserNotificationCenter`. Call/phone signals are no-op on iOS.

**Android** — add to `AndroidManifest.xml`:

```xml theme={null}
<!-- Notification interaction tracking -->
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"/>
<!-- Call-state attention signals -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

<service
    android:name="ai.synheart.behavior.SynheartNotificationListenerService"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
    android:exported="true">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService"/>
    </intent-filter>
</service>
```

`BIND_NOTIFICATION_LISTENER_SERVICE` is a **special permission** — the user grants it in Settings → Notification access, not via a runtime dialog. `READ_PHONE_STATE` is a runtime permission. Tap / scroll / typing all work without either; the Behavior consent + view-tree hooks are sufficient. Notification and call signals just won't be populated until the user grants those permissions.

### `cloudUpload`

Network only — no OS permission. Make sure `INTERNET` and `ACCESS_NETWORK_STATE` are in your Android manifest (most apps have these already).

### `syni`

On-device LLM — no OS permission. Microphone, if you wire voice input into Syni, is your app's responsibility (`NSMicrophoneUsageDescription` on iOS, `RECORD_AUDIO` on Android).

### `vendorSync`

OAuth credential for the vendor (issued by your backend, not an OS prompt). No device permission.

### `research`

No additional OS permission beyond what the underlying data types already require. If your research export covers `biosignals`, you still need the HealthKit / Health Connect grants for `biosignals`.

## Minimal implementation (Flutter)

```dart theme={null}
// 1. Initialise.
await Synheart.initialize(
  config: SynheartConfig(
    appId: 'com.example.app',
    subjectId: 'anon_user_123',
    consentConfig: ConsentConfig(
      appId: 'com.example.app',
      appApiKey: appApiKey,
    ),
  ),
);

// 2. Synheart consent — show your in-app UI, then submit.
if (!await Synheart.hasConsent('biosignals')) {
  await Synheart.consentSubmitForm(
    deviceId: deviceId,
    platform: Platform.isIOS ? 'ios' : 'android',
    formJson: jsonEncode(consentForm),
  );
}

// 3. OS permission — only after the user agreed in (2).
final os = await synheartWear.requestPermissions(
  permissions: {
    PermissionType.heartRate,
    PermissionType.hrv,
    PermissionType.sleep,
  },
);

// 4. Start streaming. Adapters gate samples on the OS layer; the runtime
//    gates fusion/upload on the Synheart layer.
if (os[PermissionType.heartRate] == ConsentStatus.granted) {
  await synheartWear.startStreaming();
}
```

Swift and Kotlin shapes are identical — see [Wear · Swift](/synheart-wear/swift), [Wear · Kotlin](/synheart-wear/kotlin), [Wear · Flutter](/synheart-wear/flutter).

## Handling denial and revocation

| Scenario                                  | What you see                                                                                           | What to do                                                                                                                                         |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| User denies Synheart consent              | `hasConsent(type) == false`; module never starts.                                                      | Show a "consent required" state. Don't request OS permission — the prompt has no value yet.                                                        |
| User denies OS permission                 | `requestPermissions` returns `denied`; `hasConsent` is still `true`.                                   | Show a rationale and re-request, or deep-link to OS settings. See [Wear · Errors](/synheart-wear/errors).                                          |
| User revokes Synheart consent mid-session | Streams keep firing; fields derived from revoked data drop out; HSI confidence on affected axes falls. | Observe `Synheart.onConsentChange` and pause UI as needed. See [What happens mid-session](/synheart-core/consent-system#what-happens-mid-session). |
| User revokes OS permission mid-session    | Adapter stops receiving samples; runtime sees an empty stream.                                         | Re-prompt the next time the feature is used.                                                                                                       |
| Cloud JWT expires                         | `getConsentStatus()` returns `expired`.                                                                | Call `ensureCloudConsentReady()` — handled automatically on most paths.                                                                            |

Local data is **not** deleted on either revocation. Call `Synheart.wipeLocalData()` if your policy requires a hard wipe.

## Common mistakes

* **Treating OS permission as consent.** A user who tapped "Allow" on HealthKit has not consented to `cloudUpload`. Each is separate.
* **Using `hasConsent` to test for OS permission.** It only reflects the Synheart layer. Use `requestPermissions` (Wear) or the platform's own permission API.
* **Calling `grantConsent` in production.** It only mutates the local snapshot; without a JWT, `hasConsent` returns `false` whenever a consent service is configured. Use `consentSubmitForm`.
* **Forgetting the Health Connect manifest plumbing on Android.** The library manifest ships empty — you must declare every `health.READ_*` permission, the rationale intent-filter, and the `ViewPermissionUsageActivity` alias yourself, or Health Connect reads silently return empty.
* **Forgetting the notification listener service registration.** Behavior consent + `BIND_NOTIFICATION_LISTENER_SERVICE` are not enough on their own — you also need the `<service>` block in your manifest, and the user must enable it in Settings → Notification access.
* **Adding `NSMotionUsageDescription` "to be safe" on iOS.** It's harmless but misleading — Synheart doesn't use the activity classifier, so the string never appears in any prompt.
* **Requesting OS permission before Synheart consent.** Mechanically valid; users decline at higher rates because the prompt arrives without context.

## Account deletion

`Synheart.requestAccountDeletion()` overrides every consent type to denied for outbound traffic, regardless of stored state. OS permissions are untouched — that's the user's call. `cancelAccountDeletion()` lifts the override.

## Related

* [Consent System](/synheart-core/consent-system) — full reference: types, tiers, channels, status, API.
* [App policy](/platform/app-policy) — the per-app dashboard layer that backstops what your SDK is allowed to offer.
* [Consent profiles](/platform/consent-profiles) — the per-app blueprints your SDK uses to render the consent UI.
* [Capability System](/synheart-core/capability-system) — the second authority alongside consent.
* [Wear · Adapters](/synheart-wear/adapters) — `PermissionType` → OS-scope mapping.
* [Behavior · Threat Model](/synheart-behavior/threat-model) — the three-layer consent model for Behavior specifically.
* [Troubleshooting](/guides/troubleshooting) — HealthKit / Info.plist gotchas.
