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 |
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 atcloud.ouraring.com/personal-access-tokens(note: Oura has been moving new integrations to OAuth2).Multi-user apps: register an OAuth2 application atcloud.ouraring.com/oauth/applicationswith redirect URInoctua://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 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 — 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.