COROS activities & Apple Health metrics — automated daily via GitHub Actions
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.
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.
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:
teamapi.coros.com/activity/query — paginated list of all activities with distance, pace, heart rate, elevation, and moreteamapi.coros.com/analyse/query — daily training analytics including resting HR, training load, fatigue rate, and acute/chronic loadteamapi.coros.com/account/login — authentication endpoint accepting email + MD5-hashed password, returning an access token
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.
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.
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.
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.
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.
fetch_coros.py authenticates with the COROS Training Hub API (MD5 password + access token), paginates through all activities, fetches daily training analytics, and writes pre-processed JSON. Runs daily at 6:30 AM UTC via GitHub Actions cron.repository_dispatch event. receive_health_data.py processes the payload and merges records by date.urllib, json, and ssl.