Health & Training Dashboard

COROS activities & Apple Health metrics — automated daily via GitHub Actions

Python Chart.js COROS API Apple Health iOS Shortcuts GitHub Actions
Back to Projects

Project Overview

This dashboard combines two automated data pipelines: a Python script that fetches training data from the COROS Training Hub API, and an iOS Shortcut that exports health metrics from Apple Health. Both feed into GitHub Actions workflows that commit updated JSON to this repository, keeping the dashboard fresh with zero manual effort.

What started as a straightforward "connect to an API and chart the data" project turned into a deep dive in reverse engineering, hitting dead ends with SSL certificate pinning, and discovering the limitations of wearable data ecosystems. Here's how it all went down.

The Journey

Step 1: Is There an API?

Unlike Strava, COROS doesn't offer a public API. There's no developer portal, no OAuth flow, no documentation. My COROS PACE 3 watch tracks everything — runs, heart rate, sleep, HRV, SpO2, training load — but COROS keeps that data locked behind their mobile app and web platform at t.coros.com (Training Hub).

So the first step was clear: open Chrome DevTools and see what the COROS Training Hub web app is actually doing under the hood.

Step 2: Reverse-Engineering the Web API

Using the Network tab in Chrome DevTools, I captured the HTTP requests the Training Hub makes as you browse your activities and analytics. Three endpoints stood out:

I exported the cURL commands from DevTools, tested them in the terminal, and confirmed I could pull all 232 activities and 84 days of training analytics. The auth pattern uses a custom accesstoken header and a yfheader JSON object containing the user ID. From there I built fetch_coros.py — a zero-dependency Python script that logs in, paginates through all activities, fetches training analytics, and writes processed JSON.

Step 3: The Missing Health Data

The web API gave me training activities and fitness analytics, but the really interesting health metrics — sleep stages, HRV, SpO2, stress — were nowhere to be found. These are prominently displayed in the COROS mobile app but apparently served from a different API domain.

I probed dozens of endpoint patterns on teamapi.coros.com trying to find sleep and health data. Every attempt returned a 500 error (not 404, meaning the routes exist but expect a different request format or come from a different API surface). The mobile app likely uses p.coros.com instead of teamapi.coros.com.

Step 4: Proxyman & SSL Pinning

To figure out what the mobile app was doing, I set up Proxyman as an HTTPS proxy to intercept traffic from my iPhone. The idea was simple: watch the COROS app make requests, capture the endpoints and payloads, and replicate them in Python.

It almost worked. I could see the COROS app connecting to p.coros.com in Proxyman's connection log. But the moment I enabled SSL proxying for that domain to decrypt the traffic, the app immediately stopped making requests. COROS implements SSL certificate pinning — the app only trusts COROS's own certificates and rejects Proxyman's generated certificate, effectively blocking any man-in-the-middle inspection.

This is actually good security practice on COROS's part, but it meant the mobile-only health endpoints were off limits without jailbreaking or more invasive reverse engineering.

Step 5: Apple Health as a Bridge

Since COROS syncs data to Apple Health, the next idea was to use Apple Health as an intermediary. The plan: COROS syncs to Apple Health → an iOS Shortcut queries Apple Health for sleep, HRV, SpO2, and resting HR → the Shortcut POSTs the data to GitHub via a repository_dispatch event → a GitHub Actions workflow processes and commits the data.

I evaluated Health Auto Export (an iOS app with REST API integration) but it requires a paid subscription for automation. Instead, I built a free iOS Shortcut that queries Apple Health, formats the data as JSON, and POSTs it directly to the GitHub API using a personal access token. I also set up an automation on my phone to trigger it automatically each day.

The backend is fully operational — receive_health_data.py processes incoming payloads, deduplicates by date, and merges with existing records. The GitHub Actions workflow was tested and confirmed working with sample data.

Step 6: The Final Plot Twist

After building the entire pipeline, I discovered that COROS doesn't actually sync health metrics to Apple Health. It only syncs workout activities and workout heart rate — not sleep, HRV, SpO2, or resting heart rate. The COROS app shows a "sync successful" confirmation, but only workout data actually flows through.

This means the Apple Health sections of this dashboard are ready and waiting, but won't show data until either COROS updates their Apple Health integration, or I find another source for those metrics. The infrastructure is built, tested, and deployed — it just needs data.

Training Stats COROS

Loading...

Activity Type Breakdown

Activities by Type

Type Distribution

Distance Over Time

Monthly Distance (Miles)

Training Load & Fitness

Daily Training Load

Acute vs Chronic Load

Activity Patterns

Day of Week Distribution

Running Pace Trend

Health Metrics Apple Health

Resting Heart Rate

Daily Resting HR (COROS)

Daily Resting HR (Apple Health)

Sleep Analysis

Nightly Sleep Stages

Nightly Sleep Duration

Average Sleep Stage Breakdown

HRV & Blood Oxygen

Heart Rate Variability Trend

Blood Oxygen (SpO2)

Architecture

Technologies Used

Python JavaScript Chart.js COROS API Apple Health GitHub Actions Data Viz

GitHub Repository

View on GitHub