# Noctua – A privacy-first Oura Ring SDK with on-device ML, no cloud

> Source: <https://github.com/RanjithRagavan/Noctua>
> Published: 2026-08-22 06:56:37+00:00

**On-device AI wellness intelligence for Oura Ring — privacy-first Android SDK.**

Noctua (the owl genus — nocturnal wisdom) is an open-source Kotlin toolkit that
combines a complete, typed client for the **Oura API v2** with an **on-device AI
layer** that turns raw biometrics into explainable insights and a next-day
readiness forecast — **without your health data ever leaving the phone**.

| Dashboard | AI Coach (on-device) | Connect |
|---|---|---|

*Captured from the example app running in demo mode on a Pixel 7 Pro emulator.*

Most wearable companion apps ship your biometric history to a cloud LLM to generate "insights". Noctua takes the opposite stance:

| Cloud AI companions | Noctua |
|
|---|---|---|
| Raw HRV / sleep / temperature data | uploaded to a server | never leaves the device |
| Insight logic | opaque | transparent, unit-tested rules + open model |
| Works offline | ✗ | ✓ |
| Latency | network round-trip | < 5 ms on-device |

``` php
graph TD
    A[Oura Cloud API v2] -->|OAuth2 / PAT| B[noctua-core<br/>typed Kotlin client]
    B --> C[WellnessSnapshot<br/>readiness · sleep · activity · HRV]
    C --> D[noctua-ai<br/>FeatureExtractor]
    D --> E1[HeuristicInsightEngine<br/>explainable rules]
    D --> E2[ExecuTorchForecaster<br/>.pte neural model, on-device]
    E1 --> F[NoctuaReport]
    E2 --> F
    E2 -.missing runtime.-> E3[LinearHeuristicForecaster<br/>zero-dependency fallback]
    E3 --> F
    F --> G[example-app<br/>Jetpack Compose]
```

| Module | What it is |
|---|---|
`noctua-core` |
Pure-Kotlin Oura API v2 client — OAuth2 helpers, auto-refreshing tokens, all `usercollection` endpoints, pagination, sandbox support. Runs on Android and any JVM backend. |
`noctua-ai` |
On-device intelligence: feature extraction (sleep debt, HRV z-score vs personal baseline, readiness trend), explainable heuristic insights, and a neural readiness forecaster bridged to ExecuTorch. |
`example-app` |
Material 3 Compose app — score rings, 14-day readiness trend, AI coach feed, OAuth/token connect flow, and a built-in demo mode that needs no Oura account. |
`model/` |
PyTorch → ExecuTorch export script for the readiness forecaster. |

**Personal use:** create a Personal Access Token at[cloud.ouraring.com/personal-access-tokens](https://cloud.ouraring.com/personal-access-tokens)*(note: Oura has been moving new integrations to OAuth2)*.**Multi-user apps:** register an OAuth2 application at[cloud.ouraring.com/oauth/applications](https://cloud.ouraring.com/oauth/applications)with redirect URI`noctua://callback`

.

The modules are plain Gradle project dependencies (publish to Maven or use
via `includeBuild`

/ JitPack):

```
dependencies {
    implementation("com.noctua:noctua-core:0.1.0")
    implementation("com.noctua:noctua-ai:0.1.0")
    // Optional: enable the neural forecaster
    implementation("org.pytorch:executorch-android:1.0.0")
}
val oura = OuraClient.Builder()
    .token("YOUR_TOKEN")
    .build()

// Coroutine-first; pagination is handled for you.
val readiness = oura.dailyReadiness(startDate = "2026-08-01", endDate = "2026-08-21")
val sleep     = oura.dailySleep(startDate = "2026-08-01", endDate = "2026-08-21")
val periods   = oura.sleep(startDate = "2026-08-01", endDate = "2026-08-21")
```

OAuth2 (client-side flow) in two lines:

```
val url = OuraOAuth.authorizationUrl(clientId, redirectUri = "myapp://callback")
// open `url` in a Custom Tab, then in your deep-link handler:
val token = OuraOAuth.parseClientSideRedirect(intent.dataString!!).accessToken
```

For long-lived apps, `OAuthTokenProvider`

refreshes expiring tokens
automatically via Oura's `refresh_token`

grant.

```
val ai = NoctuaAI()
val report = ai.analyze(WellnessSnapshot(
    readiness = readiness,
    sleep = sleep,
    activity = oura.dailyActivity("2026-08-01", "2026-08-21"),
    sleepPeriods = periods,
))

println(report.forecastedReadiness)   // e.g. 74 — tomorrow's predicted score
report.insights.forEach { println("• ${it.title} (${it.confidence}%)") }
// • Sleep debt accumulating (88%)
// • HRV below your baseline (80%)
cd model
pip install torch executorch
python export_readiness_forecaster.py   # → readiness_forecaster.pte
```

Ship the `.pte`

with your app and swap the forecaster:

```
val ai = NoctuaAI(forecaster = ExecuTorchForecaster(pteFile.absolutePath))
```

If the ExecuTorch runtime or model file is absent, Noctua silently falls back to the bundled linear model — the app never breaks.

| Endpoint | `OuraClient` method |
Scope |
|---|---|---|
`/v2/usercollection/personal_info` |
`personalInfo()` |
personal |
`daily_sleep` / `daily_readiness` / `daily_activity` |
`dailySleep()` · `dailyReadiness()` · `dailyActivity()` |
daily |
`daily_spo2` · `daily_stress` · `daily_resilience` |
`dailySpo2()` · `dailyStress()` · `dailyResilience()` |
spo2 / daily |
`daily_cardiovascular_age` · `vO2_max` |
`dailyCardiovascularAge()` · `vo2Max()` |
heart_health |
`sleep` (detailed periods) · `sleep_time` |
`sleep()` · `sleepTime()` |
daily |
`heartrate` (time series) |
`heartrate(start, end)` ISO-8601 datetimes |
heartrate |
`workout` · `session` · `tag` / `enhanced_tag` |
`workouts()` · `sessions()` · `tags()` · `enhancedTags()` |
workout / session / tag |
`rest_mode_period` · `ring_configuration` |
`restModePeriods()` · `ringConfigurations()` |
daily / ring_configuration |
Sandbox (`/v2/sandbox/...` ) |
`Builder().sandbox(true)` |
none |

Errors map to typed `OuraException`

subtypes: `Unauthorized`

, `RateLimited`

(Oura allows ~5000 req / 5 min), `Http`

, `Network`

, `Serialization`

.

```
git clone https://github.com/RanjithRagavan/Noctua.git
cd Noctua
./gradlew :example-app:installDebug
```

The app boots into **demo mode** with a deterministic 21-day dataset, so you
can evaluate the full UX — score rings, trend chart, forecast card, AI coach —
before connecting a real ring. The [screenshots above](#screenshots) show
exactly what demo mode renders.

- On-device LLM sleep coach (ExecuTorch Llama runner, fully local chat)
- Personal fine-tuning loop: retrain the forecaster nightly on-device
- Health Connect write-back (share derived insights with Android Health)
- Webhook subscription helpers (
`/v2/webhook/subscription`

) - Compose Multiplatform + iOS (KMP) port of
`noctua-ai`

Issues and PRs welcome. The heuristics in `HeuristicInsightEngine`

are
deliberately readable — improving them with better evidence is a great first
contribution. Run `./gradlew test`

before submitting.

[Apache 2.0](/RanjithRagavan/Noctua/blob/main/LICENSE) — use it in personal or commercial apps.

*Noctua is an independent open-source project and is not affiliated with,
endorsed by, or sponsored by Ōura Health Oy.*
