# Track Oura recovery next to Strava training in one sheet

> Every morning, yesterday's sleep, readiness and workout numbers land as one row in your spreadsheet so you can see how training and recovery move together.

- Workflow type: code
- Services: Oura, Strava, Google Sheets
- Categories: Personal Productivity
- Published: 2026-08-09

## What it does

- Adds one row per day to your spreadsheet every morning, covering the day before
- Puts your recovery numbers (sleep score, total sleep, sleep efficiency, readiness, HRV balance, resting heart rate, steps, active calories) side by side with the training you actually did
- Pulls each day's sessions (sport, moving time, distance, average heart rate) so a single row tells you what you did and how your body responded
- Always writes the date, even on days with no ring data or no workout, so your date column stays continuous and charts do not break

## What you'll need

- An Oura account with a ring that is syncing. Gen 3 and Ring 4 owners need an active Oura membership for data to come through
- A Strava account with your workouts recorded
- A Google account and a spreadsheet (plus the tab name) where the daily rows should collect

## Prompt

Every morning at 7am, append yesterday's recovery and training numbers into a single Google Sheets tracker, so that over time I can see how training load and recovery move together. Use a cron trigger set to 7am daily.

First work out yesterday's date in my local timezone and format it as YYYY-MM-DD. Use that same value as both the start date and the end date on every Oura call, since Oura date parameters expect YYYY-MM-DD.

Pull the recovery side from Oura. Use List Daily Sleep for sleep score, total sleep duration and sleep efficiency. Use List Daily Readiness for readiness score, HRV balance and resting heart rate. Use List Daily Activity for steps and active calories. Use List Workouts to count any workouts Oura recorded that day.

Pull the training side from Strava with List Athlete Activities, filtered to yesterday only. Strava filters activities by epoch seconds, so convert yesterday's local start and end of day into epoch timestamps for the before and after parameters, then discard anything that falls outside yesterday. From each activity capture sport type, moving time, distance and average heart rate.

If there is more than one Strava activity on the day, combine them into the one daily row rather than writing multiple rows: join the distinct sport types with a comma, sum moving time, sum distance, and take the average heart rate weighted by each activity's moving time. Ignore activities with no heart rate data when computing that weighted average.

Write exactly one row per day using Append Values, with the Oura recovery columns and the Strava training columns side by side so a single row tells me what I did and how my body responded. Use this column order: Date, Sleep Score, Total Sleep (hours), Sleep Efficiency (%), Readiness Score, HRV Balance, Resting Heart Rate, Steps, Active Calories, Oura Workout Count, Strava Sport Types, Strava Moving Time (minutes), Strava Distance (km), Strava Average Heart Rate. Convert Oura sleep duration from seconds to hours and Strava distance from meters to kilometers, and round each to one decimal place.

Never skip a date. If Oura returns no data for the day, still write the row with the date filled in and the recovery metric cells left blank rather than omitting the date entirely. Do the same on rest days, where the Strava training cells are blank. The date column has to stay continuous or the charts built on this sheet break.

Two things to handle carefully. Oura collection responses paginate, returning a next_token field, so keep requesting with that token until it comes back null before deciding a day has no data. And Gen3 and Ring 4 users need an active Oura Membership or the API simply returns empty data, so treat an empty Oura response as a legitimate blank row rather than an error, and do not fail the run over it.

If the target sheet is empty, write the header row first using the column names above, then append the day's data row beneath it.

## How to customize

- Change the 7am run time to whatever fits your morning, or run it later if your ring syncs late
- Add or drop columns, for example bring in body temperature, stress, or elevation gain
- Point it at a different spreadsheet or tab, or keep separate trackers per training block

## FAQ

### What happens on a rest day when I do not train?

The row is still written with the date and all of your recovery numbers. The training columns are simply left blank, so you can still chart sleep and readiness across the full period.

### What if my ring did not sync or I did not wear it overnight?

The date still gets its own row with blank metric cells rather than being skipped. That keeps the date column unbroken so any charts built on the sheet keep working.

### Will this work if I log more than one workout in a day?

Yes. All of that day's sessions are combined into the single daily row, with the sports listed together and the durations and distances totalled.

### Do I need a paid Oura membership?

If you have a Gen 3 ring or Ring 4, yes. An active Oura membership is required for your data to be available outside the app. Older rings are not affected.

### Can I point this at a spreadsheet that already has data in it?

Yes. New rows are added after the last row already in the sheet, so your existing history stays untouched.

### Why does it run in the morning instead of at midnight?

Oura finalises the previous night's sleep and readiness scores after you wake and the ring syncs. Running at 7am gives those numbers time to settle before the row is written.

Use this prompt in General Input: https://www.generalinput.com/prompts/track-oura-recovery-next-to-strava-training-in-one-sheet