Getting started with rCoros

rCoros gives you tidy access to your COROS Training Hub data from R. Every function returns a tibble, so you can pipe results straight into dplyr and ggplot2.

Setup

Store your credentials in ~/.Renviron so they are never hard-coded in a script:

[email protected]
COROS_PASSWORD=secret

Then restart R and authenticate:

library(rCoros)

auth <- coros_login()
auth
#> <coros_auth>
#>   region:    us
#>   user_id:   123456789
#>   logged in: 2026-06-07 08:00:00

Pass region = "eu" if your account was created in Europe. The auth object is just a lightweight list — create it once per session and pass it to every subsequent call.

Activities

coros_activities() returns one row per activity. By default it fetches the last 30 days and auto-paginates until all matching activities are returned:

library(dplyr)

acts <- coros_activities(auth)
acts
#> # A tibble: 47 × 16
#>   activity_id name            sport_type sport_name date       start_time
#>   <chr>       <chr>                <int> <chr>      <date>     <dttm>
#> 1 4780…       Morning Trail R…       102 Trail Run… 2026-06-06 2026-06-06 06:12:00
#> 2 4779…       Easy Run               100 Running    2026-06-04 2026-06-04 07:00:00
#> …

Filter to runs only, then look at distance and training load over time:

runs <- acts |>
  filter(sport_type %in% c(100L, 102L)) |>
  arrange(date)

library(ggplot2)

ggplot(runs, aes(date, distance_km)) +
  geom_col(fill = "#3A7BD5") +
  labs(title = "Weekly volume", x = NULL, y = "Distance (km)")

Activity detail

Drill into a single activity to get lap splits and heart-rate zones:

detail <- coros_activity_detail(
  auth,
  activity_id = acts$activity_id[[1]],
  sport_type  = acts$sport_type[[1]]
)

# One-row summary
detail$summary |> glimpse()

# Lap splits
detail$laps

# Time in HR zones
ggplot(detail$hr_zones, aes(factor(zone), minutes, fill = factor(zone))) +
  geom_col(show.legend = FALSE) +
  scale_fill_brewer(palette = "RdYlGn", direction = -1) +
  labs(title = "Time in HR zones", x = "Zone", y = "Minutes")

Daily wellness metrics

coros_daily_metrics() pulls per-day HRV, resting heart rate, VO2max, training load, and more for up to 90 days at a time:

metrics <- coros_daily_metrics(auth, start_day = "20260101", end_day = "20260607")

# HRV trend with baseline
ggplot(metrics, aes(date)) +
  geom_ribbon(aes(ymin = hrv_baseline - 5, ymax = hrv_baseline + 5),
              fill = "steelblue", alpha = 0.2) +
  geom_line(aes(y = hrv_baseline), colour = "steelblue", linewidth = 0.8) +
  geom_point(aes(y = hrv), size = 1.5) +
  labs(title = "Overnight HRV vs. baseline", x = NULL, y = "HRV (ms)")

For a quick view of just the last 7 days, coros_hrv() hits a lighter dashboard endpoint:

coros_hrv(auth)
#> # A tibble: 7 × 4
#>   date         hrv baseline hrv_sd
#>   <date>     <dbl>    <dbl>  <dbl>
#> 1 2026-06-01  61.2     58.4    4.1
#> 2 2026-06-02  64.8     58.8    3.9
#> …

Training load

Training load and its acute:chronic ratio are in coros_daily_metrics(). A ratio above ~1.5 is a useful proxy for injury risk:

metrics |>
  filter(!is.na(load_ratio)) |>
  ggplot(aes(date, load_ratio)) +
  geom_hline(yintercept = c(0.8, 1.3), linetype = "dashed", colour = "grey60") +
  geom_line(colour = "#E06C2C", linewidth = 1) +
  annotate("text", x = min(metrics$date), y = 1.35,
           label = "Caution zone", hjust = 0, size = 3, colour = "grey40") +
  labs(title = "Acute:chronic training load ratio",
       x = NULL, y = "Load ratio")

Workout programmes

coros_workouts() returns your saved workout library as two linked tibbles — the workout header and its individual steps:

w <- coros_workouts(auth)

# All workouts
w$workouts

# Steps for a specific workout
w$steps |> filter(workout_id == w$workouts$id[[1]])

Training calendar

coros_schedule() shows what’s planned for the next two weeks (or any window you specify):

sched <- coros_schedule(auth)

sched
#> # A tibble: 9 × 5
#>   happen_day name              sport_name estimated_min completed
#>   <date>     <chr>             <chr>               <dbl> <lgl>
#> 1 2026-06-07 Long Run          Running              90   FALSE
#> 2 2026-06-09 Recovery Run      Running              40   FALSE
#> …

Putting it together

A common pattern is to join activities back to daily metrics to explore how recovery scores relate to performance:

combined <- runs |>
  left_join(
    metrics |> select(date, hrv, hrv_baseline, rhr, load_ratio),
    by = "date"
  )

ggplot(combined, aes(hrv, avg_hr, colour = load_ratio)) +
  geom_point(size = 2.5) +
  geom_smooth(method = "lm", se = FALSE, colour = "grey40") +
  scale_colour_viridis_c(name = "Load ratio") +
  labs(
    title  = "HRV vs. average run HR",
    x      = "Overnight HRV (ms)",
    y      = "Average HR (bpm)"
  )