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

# Cloud Protocol

> How HSI snapshots travel from the SDK to Synheart Platform — endpoints, headers, and authentication

When you turn cloud upload on, Synheart Core hands HSI snapshots to the runtime's **unified ingest connector**. The connector batches, signs, and uploads them to Synheart Platform. This page is the developer-facing reference for what crosses the wire and how to authenticate.

<Note>
  The runtime owns the upload queue and signing. You enable cloud upload in your config — most apps never touch the lower-level paths described here.
</Note>

## Architecture

```mermaid theme={null}
flowchart LR
  subgraph SDK["SDK (On-Device)"]
    ENG["HSI Runtime\n(emits HSI windows)"] --> BRIDGE["Auto-enqueue bridge\n(consent-gated)"]
    BRIDGE --> Q["Persistent queue\n(SQLite)"]
    Q --> CONN["Cloud Connector\n- batch\n- retry\n- sign"]
  end

  subgraph CLOUD["Synheart Platform"]
    GATE["Gateway\n/ingest/v1/hsi"] --> AUTH["Verify\nX-Synheart-Proof\n+ X-Consent-Token"]
    AUTH --> SCHEMA["Schema validate"]
    SCHEMA --> STORE["Tenant-isolated storage"]
  end

  CONN -->|"HTTPS POST"| GATE
```

The connector is part of the Synheart runtime (native). The Dart/Swift/Kotlin SDKs do not maintain their own queues.

## Core principles

1. **Consent-gated** — every upload checks the runtime's consent state before sending.
2. **Derived data only** — only HSI snapshots leave the device. No raw biosignals, no clipboard contents, no notification bodies.
3. **Hardware-backed authentication** — every request is signed by an ECDSA P-256 key in the device's Secure Enclave (iOS) or Android Keystore.
4. **Per-request consent token** — every upload carries a JWT issued by the consent service.
5. **Tenant-isolated** — payloads route to per-`org_id` storage; no cross-tenant access.
6. **Offline-resilient** — the runtime persists its queue in SQLite; uploads resume after restart and reconnect.
7. **Rate-limited** — the runtime caps outbound flow at 10 uploads/sec, burst 20.

## Endpoint

The runtime POSTs to:

```http theme={null}
POST <api_base_url>/ingest/v1/hsi
Content-Type: application/json
```

When the lab feature is enabled and `Research` consent is granted, the runtime also POSTs to:

```http theme={null}
POST <api_base_url>/ingest/v1/lab/session
```

`<api_base_url>` is `https://api.synheart.ai` by default; enterprise customers can override via the `SYNHEART_API_URL` environment variable or `synheart.json`.

## Authentication

Every request carries two pieces of authentication:

### `X-Synheart-Proof`

A compact JWS (JSON Web Signature) bound to this specific request. Built by the runtime via `synheart_core_sdk_build_proof_header(method, url)`.

The proof binds:

* The HTTP method.
* The signing path (the runtime strips the `/ingest/` prefix; signing is over `/v1/hsi`).
* The device's registered `device_id`.
* A subject hash.
* A fresh nonce.

The signature is produced by the device's hardware key — your app does not handle private key bytes.

<Note>
  The runtime signs `/v1/hsi` even though the request is POSTed to `/ingest/v1/hsi`. If you build clients that talk to `/ingest/...` endpoints from outside the runtime, mirror this prefix-stripping rule.
</Note>

### `X-Consent-Token`

A JWT issued by the consent service when the user grants `cloudUpload` consent. Carries:

* `profile_id` — which consent profile the user accepted.
* `expires_at` — token validity (refreshed at the 5-minute threshold).
* `scopes` — granted channels (e.g. `bio:vitals`, `cloud:upload`).

The runtime keeps this token in memory and refreshes it before each flush.

### Request signing

Requests are signed with **hardware-backed ECDSA P-256 plus attestation** per `RFC-AUTH-MOBILE-0001`. HMAC is not used for cloud ingest. See [Synheart Auth](/synheart-auth/overview) for the full device-identity flow.

## Pre-conditions for upload

Before any HSI window leaves the device, the runtime checks:

1. `IngestConfig.enabled = true` and `IngestConfig.hsi = true` (or `lab = true` for lab uploads).
2. The capability level permits cloud upload.
3. `cloudUpload` consent is granted with a valid token.
4. The account-deletion override is not active.
5. Device-bound crypto callbacks are registered (the host's `synheart-auth` integration).
6. Network reachability (best-effort).

Any failure short-circuits the upload; the window stays in the queue (or buffer) and retries on the next cycle.

## Cold-start consent buffer

The runtime opens an HSI session immediately, but the consent token may take a few seconds to fetch on cold start. To avoid losing the first window:

* The auto-enqueue bridge holds up to **8 pending HSI windows** in a FIFO buffer when consent is `pending`.
* On grant, the buffer drains and replays into the queue.
* If consent never arrives within the buffer window, the oldest windows drop FIFO.

This is automatic — your app does not configure or observe it directly.

## Payload shape

Every upload is an HSI 1.3 JSON envelope produced by the engine. The exact field set follows the canonical [HSI specification](/hsi/overview); the SDK does not reshape it before sending.

```jsonc theme={null}
{
  "hsi_version": "1.3",
  "observed_at_utc": "2026-01-15T10:30:00Z",
  "computed_at_utc": "2026-01-15T10:30:00Z",
  "producer": {
    "name": "synheart-core",
    "version": "0.0.4",
    "instance_id": "<device-scoped uuid>"
  },
  "window_ids": [...],      // see hsi/overview for the full shape
  "windows": { ... },
  "axes": { ... },
  "privacy": { "contains_pii": false }
}
```

The `subject_id` you supply in `SynheartConfig` is **never sent in the clear** — the runtime hashes it into a per-tenant `subject_hash` for identity routing, and that's what reaches the cloud.

## Endpoint selection by capability

| Capability level | HSI endpoint                             | Lab endpoint             |
| ---------------- | ---------------------------------------- | ------------------------ |
| `core`           | `/ingest/v1/hsi`                         | (lab not available)      |
| `extended`       | `/ingest/v1/hsi` (richer payload fields) | `/ingest/v1/lab/session` |
| `research`       | `/ingest/v1/hsi` (full fields)           | `/ingest/v1/lab/session` |

The level controls which fields the engine includes in the envelope (e.g. embedding presence). The endpoint path is the same; differentiation happens server-side via the capability token.

## Lab session uploads

When `enable lab ingest` is set in the SDK config and the user grants `research` consent, the runtime auto-enqueues the JSON returned by `Synheart.labFinalize(...)`. The runtime stamps the active lab metadata id into `session_metadata.extra_data.metadata_id` before sending.

## Backlog and offline behavior

* **Persistent queue:** the runtime's queue is SQLite at `<data_dir>/ingest_upload_queue_<subject_id>.sqlite`. Survives app restarts.
* **Network monitor:** the runtime watches `<api_base_url>/health` and exposes reachability via `Synheart.isNetworkReachable`.
* **Backoff:** failed uploads use exponential backoff with jitter. Persistent 4xx failures (malformed payload, rejected token) are quarantined and surfaced through the connector's metadata.

## Inspecting upload state

```dart theme={null}
// Force a flush via the ingestion façade. Checks consent before draining.
final result = await Synheart.ingestion.flushIfEligible();
print('uploaded=${result.uploaded} failed=${result.failed} requeued=${result.requeued}');

// Current queue depth (synchronous getter).
final depth = Synheart.uploadQueueLength;

// Last successful upload (epoch ms; null/0 means "never").
final lastMs = Synheart.lastIngestSuccessAtMs;

// Runtime diagnostics: availability, version, frame count, last quality.
final diag = Synheart.runtimeDiagnostics();
```

`Synheart.ingestion.flushIfEligible()` is the canonical entry point for manually draining the queue. `Synheart.ingestion.enqueueHsiWindows(...)` enqueues HSI JSON windows directly. The Kotlin and Swift SDKs expose the same surface with platform-idiomatic names.

## Errors

Common failures and how to handle them:

| Failure                                   | Cause                                             | Recovery                                                                              |
| ----------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Queue grows but never drains              | Consent token expired or never issued             | Call `Synheart.ensureCloudConsentReady()` and confirm `consentStatus()` is `granted`. |
| Upload rejected with auth error           | Device not registered or proof invalid            | Call `Synheart.ensureDeviceAuthRegistered()`.                                         |
| Upload returns 4xx with malformed payload | Engine emitted a payload the gateway rejects      | File a bug — the runtime should not produce invalid HSI.                              |
| Construction errors mentioning `org_id`   | `cloudConfig.orgId` empty when HSI ingest enabled | Set `CloudConfig.orgId` in your config.                                               |

## Privacy guarantees enforced in code

* Raw `subject_id` never leaves the device — only `subject_hash`.
* Raw biosignals never leave the device — only HSI snapshots (and lab session JSON when explicitly enabled).
* Cloud-bound traffic gates on consent at every flush; revocation immediately pauses the connector via the runtime's consent-check hook.
* TLS 1.2+ for HTTPS; gRPC streams (RAMEN) use `tonic` with `tls-webpki-roots`.
* Account-deletion mode (set via `Synheart.requestAccountDeletion()`) fully suspends outbound traffic.

## Wipe vs. account deletion

* `Synheart.wipeLocalData()` clears the runtime's local stores (queue, history, sessions, HSI windows). Does **not** delete cloud data.
* `Synheart.requestAccountDeletion()` puts the runtime in wind-down mode and signals the platform to delete cloud data. Call `cancelAccountDeletion()` to undo before the platform confirms.

## Related

* [Synheart Auth — Request Signing](/synheart-auth/signing) — the `X-Synheart-Proof` contract.
* [Consent System](/synheart-core/consent-system) — `cloudUpload` consent, token lifecycle.
* [Capability System](/synheart-core/capability-system) — what each level unlocks.
* [HSI Specification](/hsi/overview) — payload schema (HSI 1.3).
