Poking around, I found out that Oura ring has a robust API, so I wanted to see what Claude Code could do with it. It took an afternoon of fiddling, but it seems to be a robust custom data board with about 4 years of data, that updates itself 3 times a day to a SQLite database. It runs on my Mac Mini home server.

I can download my CSV data from the data board, and I also set it up to run a monthly report that makes an Apple Note for me and files it away. I had it run monthly reports for the past 4 years, and it’s an excellent way to digest sleep data.

The best part is a second page on the dashboard called the Correlation Explorer, where you can see how tag data affects your sleep. You do have to remember to add tags, late meals, massage, medication, sauna, vaccinations, etc., and you can start to see what is helping and hurting your rest. You can see the evidence on my board. On this note, my wife swears that a hot shower before bed helps you fall asleep faster. That’s the next test that will be tracked with tags. We shall see.

I know a lot of people don’t like A.I., but I’m still in awe of the things I can build with it. This is so far beyond my skill level. And the data analysis that I can now do is truly helping my sleep. The older I’ve gotten, the worse my sleep has been. I’ve increased my sleep by one hour per night just from the Oura ring data.

I like the Oura ring app, and they have their own web view that is really nice, but being able to customize what you want to see and how is pretty neat.

If you have any trouble with sleep, I highly recommend using the Oura ring for 6 months or so, using tags, and figuring out what helps and hurts your sleep.

Auto-generated description: A comprehensive dashboard displays health and activity metrics, including sleep patterns, heart rate, and daily movements, against a gradient background. Auto-generated description: A correlation explorer displays numerical data linking various tags to different aspects of sleep quality, showcasing metrics such as baseline scores, tags for better sleep, and tags for worse sleep on a gradient background.

For the curious, here are some technical details.

The Stack Snapshot:

  • Next.js 14 (App Router) + TypeScript + Tailwind on the frontend and API layer.
  • SQLite via better-sqlite3 – synchronous API, ~23MB database holding ~4.4 years of history.
  • Node.js v22, brew-pinned on the prod host.
  • PM2 managing the Next.js process on port 3001 (alongside a sibling “Status Board” app on 3000).
  • launchd for scheduled jobs (not cron – it’s macOS).
  • Tailscale for access; the dashboard has no public internet exposure.
  • About 2,800 lines across the key files (ingest, queries, schema, pages), 22 React components.

The Data Pipeline:

Pulls 16 endpoints from Oura’s v2 API on a 14-day incremental window per run. Deliberately skips heartrate and interbeat_interval – those would 10x the DB for no visible dashboard benefit, and can be backfilled later if HRV analysis ever happens.

  • Idempotent by design. Every row is an INSERT OR REPLACE on the Oura-assigned document ID (with UNIQUE(day) constraints on daily tables). Re-running ingest is a safe no-op; late-arriving recomputes overwrite stale scores.
  • Two-column storage pattern. Each table has explicit parsed columns for fast queries plus a raw TEXT NOT NULL column holding the entire API JSON. If I want to derive a new column later, I don’t need to re-pull the API – just run a query over the raw JSON. Full-fidelity audit log, zero lossy transformations.
  • Every run is logged to an ingest_runs table (endpoint, start/end dates, status, doc counts, errors). Makes it trivial to see “did the 8am run land last night’s sleep yet?” – which I used twice this week.
  • ~150 API requests, ~5 seconds per run – well under Oura’s 5000 req / 5 min limit, which leaves tons of room for backfill runs.

Scheduling:

launchd fires npm run ingest three times daily (8:00, 12:00, 16:00). The multiple fires exist because Oura’s detailed sleep endpoint publishes ~1–3h post-wake, so the 8am run catches the score, later runs catch the detail. A separate monthly launchd job fires on the 1st at 9:15am to produce a digest.