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.

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

Architecture

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

Why not HMAC?

Earlier drafts described an HMAC-SHA256 flow with shared secrets. The shipping implementation uses hardware-backed ECDSA P-256 with attestation per RFC-AUTH-MOBILE-0001. HMAC is not used for cloud ingest. See the Synheart Auth section 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. 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; the SDK does not reshape it before sending.
{
  "hsi_version": "1.3",
  "observed_at_utc": "2025-12-28T10:30:00Z",
  "computed_at_utc": "2025-12-28T10:30:00Z",
  "producer": {
    "name": "synheart-core",
    "version": "0.0.4",
    "instance_id": "<device-scoped uuid>"
  },
  "window_ids": [...],
  "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 levelHSI endpointLab 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

// 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:
FailureCauseRecovery
Queue grows but never drainsConsent token expired or never issuedCall Synheart.ensureCloudConsentReady() and confirm consentStatus() is granted.
Upload rejected with auth errorDevice not registered or proof invalidCall Synheart.ensureDeviceAuthRegistered().
Upload returns 4xx with malformed payloadEngine emitted a payload the gateway rejectsFile a bug — the runtime should not produce invalid HSI.
Construction errors mentioning org_idcloudConfig.orgId empty when HSI ingest enabledSet 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.