> ## Documentation Index
> Fetch the complete documentation index at: https://docs.parsaa.app/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Configuration

> CaptureConfiguration and VitalsConfiguration, field by field

Both modules start the same way: one call, once, early, on the main thread. Each returns immediately.

<CodeGroup>
  ```swift Both modules theme={null}
  import Orbit

  let ingestURL = URL(string: "https://platform.parsaa.app/api/orbit")!

  Orbit.Vitals.start(
      configuration: VitalsConfiguration(
          ingestURL: ingestURL,
          sdkKey: "orbit_sdk_...",
          enabled: userGrantedDiagnosticsConsent
      )
  )

  Orbit.Capture.start(
      configuration: CaptureConfiguration(
          ingestURL: ingestURL,
          sdkKey: "orbit_sdk_...",
          enabled: userGrantedDiagnosticsConsent
      )
  )
  ```

  ```swift Capture only theme={null}
  import OrbitCapture

  Capture.start(
      configuration: CaptureConfiguration(
          ingestURL: URL(string: "https://platform.parsaa.app/api/orbit")!,
          sdkKey: "orbit_sdk_...",
          enabled: userGrantedDiagnosticsConsent,
          replayEnabled: true,
          revenueEnabled: true,
          appGroupIdentifier: "group.app.example"
      )
  )
  ```
</CodeGroup>

<Warning>
  `enabled` is the privacy opt in and it defaults to `false`. Collection is off until you turn it on, so ask the person using your app first, and pass their answer here rather than a literal `true`.
</Warning>

## Where the SDK key comes from

<Steps>
  <Step title="Open Settings, SDK">
    In the console, **Settings**, then **Project**, then **SDK** (`/settings/project/sdk`).
  </Step>

  <Step title="Create a key">
    Keys are created there and shown **once**: only the hash is stored, so losing one means creating another, not recovering it.
  </Step>

  <Step title="Put it in the build, not the repository">
    It belongs in the app's build configuration or a secret store. A revoked key is refused for any app still sending it.
  </Step>
</Steps>

The key is write only. It grants no read access and carries no user identity, and it is sent as the `X-Orbit-Key` header. There is no `Authorization` header anywhere on the ingest surface.

## The ingest URL

`ingestURL` is the origin, **with no path**: the SDK appends `/v1/ingest/vitals`, `/v1/ingest/events` and `/v1/ingest/policy` itself. For the hosted console that is `https://platform.parsaa.app/api/orbit`.

## CaptureConfiguration

| Field                  | Default                                                | Meaning                                                                |
| ---------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------- |
| `ingestURL`            | required                                               | Orbit ingest origin, no path                                           |
| `sdkKey`               | required                                               | Write-only project key, sent as `X-Orbit-Key`                          |
| `enabled`              | `false`                                                | The privacy opt in                                                     |
| `appVersion`           | `nil`                                                  | Overrides `CFBundleShortVersionString`                                 |
| `ringCapacity`         | `500`                                                  | In-memory events before the oldest is dropped                          |
| `maxAttrBytes`         | 2 KB                                                   | Attribute budget per event                                             |
| `redaction`            | `email`, `phone`, `token`, `password`, `authorization` | Attribute keys always dropped, on the device                           |
| `flushOnBackground`    | `true`                                                 | Flush when the app backgrounds or terminates                           |
| `automaticBreadcrumbs` | `true`                                                 | Lifecycle and memory warning breadcrumbs                               |
| `maxQueuedBatches`     | `20`                                                   | On-disk queue cap by batch count                                       |
| `maxQueuedBytes`       | 2 MB                                                   | On-disk queue cap by size                                              |
| `networkEnabled`       | `false`                                                | Turns on `URLSession` instrumentation                                  |
| `logsEnabled`          | `false`                                                | Turns on reading your own `error` and `fault` `os_log` entries         |
| `logMessagesEnabled`   | `false`                                                | Also sends the log text (see the warning below)                        |
| `layoutSource`         | `nil`                                                  | A custom replay source, when you are not using `enableReplay(window:)` |
| `replayEnabled`        | `false`                                                | The local half of the wireframe replay opt in                          |
| `layoutByteCap`        | 2 MB                                                   | Per-session budget for encoded layout JSON                             |
| `revenueEnabled`       | `false`                                                | The local half of the StoreKit 2 revenue opt in                        |
| `appGroupIdentifier`   | `nil`                                                  | Share the queue and the installation id with extensions                |

### The three fields that need two switches

`replayEnabled` and `revenueEnabled` are gated **twice**: your app opts in locally, and the project's capture policy allows the surface in Settings, Telemetry. Neither side can turn one on alone, and the same is true in reverse: three lines of replay integration can sit in a build that never records a frame.

`appGroupIdentifier` is the one that changes identity. Set it and the app and its extensions share one queue and one installation id, so a widget tap and the session it leads to are the same person.

<Warning>
  `logMessagesEnabled` is a second opt in, and it deserves its own decision. `os_log` redaction protects a line from **other** processes; a process reading its own entries gets the private interpolations back in the clear, with no marker to test for. Turning it on is your statement that your `error` and `fault` lines carry nothing about a person.
</Warning>

## VitalsConfiguration

| Field               | Default  | Meaning                                               |
| ------------------- | -------- | ----------------------------------------------------- |
| `ingestURL`         | required | Orbit ingest origin, no path                          |
| `sdkKey`            | required | Write-only project key                                |
| `enabled`           | `false`  | The privacy opt in                                    |
| `appVersion`        | `nil`    | Overrides `CFBundleShortVersionString`                |
| `sendDaily`         | `true`   | Flush at most once per 24 hours in addition to launch |
| `maxQueuedPayloads` | `50`     | Queue cap by count, oldest evicted first              |
| `maxQueuedBytes`    | 5 MB     | Queue cap by size, oldest evicted first               |

## The rest of the lifecycle

```swift theme={null}
Orbit.Vitals.flush()  // send now, ignoring the 24 h cadence
Orbit.Vitals.stop()   // unregister; queued payloads stay on disk
Orbit.Vitals.purge()  // delete everything queued on the device

Capture.flush()
Capture.stop()
Capture.purge()       // also deletes the installation id
```

## Attributes are scalars, by the compiler

`Capture.track` and `Capture.setUser` take strings, integers, doubles and booleans. A nested object, an array or a `Data` blob cannot be passed at all: it is a compile-time guarantee rather than a runtime check, so there is no shape of call that accidentally ships a payload.

## Sending is still the server's call

`enabled` says the SDK **may** send. Whether it **does** is the project's capture policy, fetched from `GET /v1/ingest/policy` at `start()` and on a cadence after it. When that request fails, returns a non-2xx status or does not parse, the SDK falls back to capture disabled: nothing leaves the device without a server yes. See [Capture policy](/orbit/capture-policy).

<CardGroup cols={2}>
  <Card title="What the SDK collects" icon="list-check" href="/orbit/what-the-sdk-collects">
    What is automatic and what your app has to call.
  </Card>

  <Card title="Capture policy" icon="toggle-on" href="/orbit/capture-policy">
    The eight surfaces, sampling, targets, workflows and the kill switch.
  </Card>
</CardGroup>
