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

# Runtime configuration

> Where Synheart Core Runtime reads its identity, API key, and URLs from — and how to map values issued by the Synheart Platform into your app bundle.

The Synheart Core Runtime needs a small set of bootstrap values to start: who your app is (`app_id`), who issued it (`org_id`), and an API key to authenticate against the platform (`app_api_key`). All three are issued by the [Synheart Platform](https://platform.synheart.ai). This guide explains where to find them and how to thread them into the runtime.

## Required values

The runtime accepts these in `SynheartConfig`. The first three come from the platform; the fourth is set per-user at runtime.

| Field         | Source                                                          | Notes                                                                                                                                                              |
| ------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `app_id`      | Platform — App detail page, or credentials JSON                 | Platform-issued identifier, e.g. `app_focus_and_kB8mPx`. Sent in every ingest call.                                                                                |
| `org_id`      | Platform — Org switcher, or credentials JSON                    | Identifier of the organization that owns the app, e.g. `org_focus_kB8mPx`.                                                                                         |
| `app_api_key` | Platform — App → **API keys** tab, **one-time creation dialog** | The **secret key**. Shown once when you create the key — not included in the credentials JSON download. Treat it like any other credential.                        |
| `subject_id`  | Your app, at runtime                                            | The end-user identifier. **Never** bake this into `synheart.json`. Pass it via `with_subject_id(...)` (or `SynheartConfig(subjectId: ...)` on mobile) per session. |

Auto-detected by the SDK (you don't set these): `device_id`, `platform`, `app_version`. Source: `synheart_config.rs:148-176`.

The portal exposes **two different credentials JSON downloads**:

* The **app detail page** lets you download a four-field identifiers-only file — `org_id`, `tenant_id`, `project_id`, `app_id`. No secrets:

  ```json theme={null}
  {
    "org_id": "org_focus_kB8mPx",
    "tenant_id": "ten_focus_kB8mPx_dev_a1b2c3",
    "project_id": "prj_focus_kB8mPx_PJI2AxsO",
    "app_id": "app_focus_and_kB8mPx"
  }
  ```

* The **Generate API Key** one-time success dialog offers a richer download that bundles the new key with the identifiers — adds `secret_key` (your `app_api_key`) and, when issued, `signing_secret`. This file appears once and never again — save it to your secret manager immediately.

Either way, the runtime only reads `org_id`, `app_id`, and `app_api_key` (which maps from `secret_key`). `tenant_id` and `project_id` are useful for direct REST calls into the platform (ingestion, data deletion) but are not parsed into `SynheartConfig`. The `signing_secret` is **not for the SDK** — see the next section.

## Where to find them on the platform

1. Sign in at [`platform.synheart.ai`](https://platform.synheart.ai) and switch to the right organization.
2. Open the project that owns your app, then open the app.
3. On the app detail page you can download the **credentials JSON** — `org_id`, `tenant_id`, `project_id`, `app_id`. No secrets are in this file.
4. Open the **API keys** tab → **Generate API Key**. In the side panel:
   * **Choose App** — pick the app this key authorises (one key, one app).
   * **Key Type** — `Production`, `Development`, `Staging`, `Testing`, or `Other`. Used for your own bookkeeping; mint a separate key per environment so you can revoke them independently.
   * **Scopes** — tick at least one of `Read`, `Write`, `Delete`, `Admin`. The form blocks submission until one is selected.
5. The portal returns the **secret key** and a **signing secret** in a one-time dialog. Copy both into your secret manager before closing — they are stored hashed and cannot be recovered. The secret key is your `app_api_key`, sent on every request as the `x-api-key` header.

Full walkthrough: [Platform — API keys](/platform/api-keys). Ingestion access for biosignals is gated separately by an app-level allowlist — see the [Ingestion access](/platform/api-keys#ingestion-access) section.

## Packaging into `synheart.json`

The runtime reads from a `synheart.json` file baked into your app bundle. This is the recommended path on every platform.

```json theme={null}
{
  "app_id": "app_focus_and_kB8mPx",
  "org_id": "org_focus_kB8mPx",
  "app_api_key": "sk_live_abc123..."
}
```

Take the four-field credentials JSON from the portal and add `app_api_key` from the one-time secret dialog. You can also keep `tenant_id` and `project_id` in the file — the runtime ignores them, but having them in one place is convenient if your backend code reads the same file.

Where the runtime looks for it (in order; source: `synheart_config.rs:212-246`):

1. Explicit path passed to `SynheartConfig::from_file(Some("…/synheart.json"))`.
2. `./synheart.json` in the process's current working directory.
3. On desktop builds only: the platform data directory (`~/.synheart/synheart-core/synheart.json` and equivalents).

On mobile, ship the file inside your app's resources and pass its path explicitly:

```dart theme={null}
// Flutter
final cfg = await rootBundle.loadString('assets/synheart.json');
await Synheart.initialize(
  config: SynheartConfig.fromJson(cfg).copyWith(subjectId: currentUserId),
);
```

```swift theme={null}
// Swift
let url = Bundle.main.url(forResource: "synheart", withExtension: "json")!
let json = try String(contentsOf: url)
let config = SynheartConfig.fromJson(json).withSubjectId(currentUserId)
try await Synheart.initialize(config: config)
```

```kotlin theme={null}
// Kotlin
val json = context.assets.open("synheart.json").bufferedReader().use { it.readText() }
val config = SynheartConfig.fromJson(json).copy(subjectId = currentUserId)
Synheart.initialize(context = context, config = config)
```

## Resolution order

When the same field is set in multiple places, the runtime takes the highest-priority source (source: `synheart_config.rs:121-126`):

1. **Values passed directly to `SynheartConfig`** (highest priority — overrides everything).
2. **`synheart.json`** (recommended for `app_id` / `app_api_key` / `org_id`).
3. **`SYNHEART_API_URL` environment variable** (only for `api_base_url`).
4. **Compiled defaults** (lowest — `api_base_url` defaults to `https://api.synheart.ai`).

This means: in development you can override `app_api_key` from a constructor argument or environment variable in your own code, while production builds read everything from `synheart.json`. The runtime itself only reads one OS env (`SYNHEART_API_URL`).

## Enterprise / self-hosted overrides

For customers running their own Synheart infrastructure, every service URL can be overridden independently. Add an `enterprise` block to `synheart.json` (source: `synheart_config.rs:92-101`):

```json theme={null}
{
  "app_id": "...",
  "app_api_key": "...",
  "org_id": "...",
  "enterprise": {
    "auth_url": "https://auth.synheart.example.com",
    "sync_url": "https://sync.synheart.example.com",
    "consent_url": "https://consent.synheart.example.com",
    "cloud_url": "https://cloud.synheart.example.com",
    "platform_url": "https://platform.synheart.example.com",
    "lab_url": "https://lab.synheart.example.com",
    "capabilities_url": "https://capabilities.synheart.example.com"
  }
}
```

Any field left out falls back to the resolved `api_base_url`. If you only need to redirect everything to a single mirror, set `SYNHEART_API_URL=https://api.example.com` and skip the enterprise block.

## What about the signing secret?

The Generate API Key dialog issues a `signing_secret` alongside the `secret_key`. **The on-device SDK doesn't use it.** There is no field for it in `SynheartConfig`, and adding one to `synheart.json` is a no-op.

The two secrets target different callers (RFC dual-mode auth):

| Secret                            | Used by                       | How                                                                               |
| --------------------------------- | ----------------------------- | --------------------------------------------------------------------------------- |
| `secret_key` (your `app_api_key`) | Everyone                      | Sent on every request as the `x-api-key` header.                                  |
| `signing_secret`                  | Server-to-server callers only | HMAC-SHA256 over `timestamp + raw_body`, sent as `X-Synheart-Signature` (Mode B). |

On device, the SDK signs ingest requests with an **ECDSA P-256 device key** that `synheart-auth` auto-registers on first launch — not with `signing_secret`. So unless your backend calls the platform REST API directly (a Postman flow, a server-side ingest pipeline, a custom integration), you can ignore the signing secret entirely.

If you do need it server-side, keep it out of the mobile bundle and store it next to your other backend credentials.

## Capability tokens

Capability tokens are separate from the app API key. The API key authenticates *your app* to the platform; capability tokens authorise *specific modules and channels* (focus estimation, emotion estimation, research export) for a given subject. They flow into the same `synheart.json`:

```json theme={null}
{
  "app_id": "...",
  "app_api_key": "...",
  "org_id": "...",
  "capability_token": "eyJ...",
  "capability_secret": "..."
}
```

For development you can pass `allowUnsignedCapabilities: true` instead, and the runtime will run without a signed token (with a warning). Don't ship that flag to production. See [Capability System](/synheart-core/capability-system) for the full token shape, signing model, and what each capability unlocks.

## Common mistakes

* **Committing `synheart.json` to a public repo.** The `app_api_key` is a long-lived secret. Add the file to `.gitignore` and ship it via your build pipeline's secret store.
* **Re-using one key across environments.** Mint a separate key per environment tag (`development`, `staging`, `production`) so you can revoke them independently. Revocation is immediate (`401` on the next request).
* **Putting `subject_id` in `synheart.json`.** That file is baked into the bundle and shared across every install; the subject is per-user and must be set at runtime.
* **Expecting `SYNHEART_API_URL` to override anything else.** It only resolves `api_base_url`. To override individual service URLs, use the `enterprise` block.
* **Forgetting the ingestion allowlist.** A valid key still gets `403` if the app hasn't been allowlisted for biosignal ingestion. Email Synheart with the `app_id` to request access — there's no self-service flow today.
* **Using `allowUnsignedCapabilities` in production.** It skips capability verification entirely. Use a signed `capability_token` instead.

## Related

* [Platform — API keys](/platform/api-keys) — generating, scoping, rotating, and revoking keys.
* [Platform — Developer portal](/platform/developer-portal) — org / project / app setup.
* [Capability System](/synheart-core/capability-system) — what `capability_token` authorises.
* [Implementing consent](/guides/consent) — the other thing your app needs before the runtime ingests data.
* [Install the runtime](/setup/install-runtime) — getting the SDK into your project.
