Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no cardYour first API call in 2 minutes →
Suunto API to FHIR: sleep, heart rate and steps (with code)
Read the Suunto Cloud API (API Zone, subscription key) and store sleep, heart rate and steps as FHIR R4 Observations, with limits and webhooks.
The Suunto Cloud API, offered through Suunto's API Zone, gives your app a person's workouts, 24/7 heart rate, HRV, steps and SpO₂, sleep stages and recovery after they approve your OAuth app. Access is through the Suunto partner programme, for companies and organisations, with a subscription key on every call. You can store each reading as a LOINC-coded FHIR R4 Observation with one standard API call. Anpheros does not connect to Suunto's cloud itself: your code reads Suunto, and Anpheros keeps the record with the person's consent. On iPhone, the Suunto app connects with Apple Health; Health Connect sync is not documented by Suunto.
Read Suunto data and store it in FHIR
First, the person's sleep from the Suunto 247 data API (a JWT access token from your Suunto OAuth app, plus your API Zone subscription key; from and to are epoch milliseconds, up to 28 days apart):
curl "https://cloudapi.suunto.com/247samples/sleep?from=1793491200000&to=1793750400000" \
-H "Authorization: Bearer $SUUNTO_TOKEN" \
-H "Ocp-Apim-Subscription-Key: $SUUNTO_SUBSCRIPTION_KEY"
Then the sleep duration as a FHIR Observation in the person's record (sandbox key sk_test_…):
curl -X POST https://platform.anpheros.com/fhir/R4/Observation \
-H "Authorization: Bearer $ANPHEROS_KEY" \
-H "Content-Type: application/fhir+json" \
-H "X-Anpheros-Author-Type: device" \
-d '{"resourceType":"Observation","status":"final",
"category":[{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/observation-category","code":"activity"}]}],
"code":{"coding":[{"system":"http://loinc.org","code":"93832-4","display":"Sleep duration"}]},
"subject":{"reference":"Patient/<id>"},
"effectivePeriod":{"start":"2026-11-02T22:50:00+01:00","end":"2026-11-03T06:35:00+01:00"},
"valueQuantity":{"value":441,"unit":"min","system":"http://unitsofmeasure.org","code":"min"},
"device":{"display":"Suunto watch (Suunto app)"}}'
The same in Python: every night becomes one sleep-duration Observation and one night heart-rate Observation, sent together in one transaction Bundle.
import os, requests
FHIR = "https://platform.anpheros.com/fhir/R4"
fhir = {"Authorization": f"Bearer {os.environ['ANPHEROS_KEY']}", "Content-Type": "application/fhir+json",
"X-Anpheros-Author-Type": "device"}
CAT = "http://terminology.hl7.org/CodeSystem/observation-category"
UCUM = "http://unitsofmeasure.org"
# Nights your backend already read from GET /247samples/sleep (stage durations, HRAvg, HRMin);
# map them to start, end, minutes asleep and heart rate as Suunto's API Zone reference describes.
sleep_by_night = [
{"start": "2026-11-01T23:05:00+01:00", "end": "2026-11-02T06:48:00+01:00", "minutes": 430, "hr_avg": 54, "hr_min": 46},
{"start": "2026-11-02T22:50:00+01:00", "end": "2026-11-03T06:35:00+01:00", "minutes": 441, "hr_avg": 53, "hr_min": 45},
]
def obs(n, category, loinc, display, quantity, components=None):
r = {"resourceType": "Observation", "status": "final",
"category": [{"coding": [{"system": CAT, "code": category}]}],
"code": {"coding": [{"system": "http://loinc.org", "code": loinc, "display": display}]},
"subject": {"reference": "Patient/<id>"},
"effectivePeriod": {"start": n["start"], "end": n["end"]},
"valueQuantity": quantity, "device": {"display": "Suunto watch (Suunto app)"}}
if components:
r["component"] = components
return {"request": {"method": "POST", "url": "Observation"}, "resource": r}
bpm = lambda v: {"value": v, "unit": "/min", "system": UCUM, "code": "/min"}
entries = []
for n in sleep_by_night:
entries.append(obs(n, "activity", "93832-4", "Sleep duration",
{"value": n["minutes"], "unit": "min", "system": UCUM, "code": "min"}))
entries.append(obs(n, "vital-signs", "8867-4", "Heart rate", bpm(n["hr_avg"]),
[{"code": {"text": "minimum"}, "valueQuantity": bpm(n["hr_min"])}]))
r = requests.post(FHIR, headers=fhir, json={"resourceType": "Bundle", "type": "transaction", "entry": entries})
print(r.status_code, len(entries), "Observations saved")
And in JavaScript (Node 18+), steps per day:
const fhir = { Authorization: `Bearer ${process.env.ANPHEROS_KEY}`, 'Content-Type': 'application/fhir+json' };
// Daily totals from GET /247samples/daily-activity-statistics; map Suunto's fields to day and steps.
const stepsByDay = [{ day: '2026-11-01', steps: 7820 }, { day: '2026-11-02', steps: 12405 }];
for (const d of stepsByDay) {
await fetch('https://platform.anpheros.com/fhir/R4/Observation', {
method: 'POST', headers: fhir,
body: JSON.stringify({
resourceType: 'Observation', status: 'final',
category: [{ coding: [{ system: 'http://terminology.hl7.org/CodeSystem/observation-category', code: 'activity' }] }],
code: { coding: [{ system: 'http://loinc.org', code: '41950-7', display: 'Number of steps in 24 hour Measured' }] },
subject: { reference: 'Patient/<id>' },
effectivePeriod: { start: `${d.day}T00:00:00+01:00`, end: `${d.day}T23:59:59+01:00` },
valueQuantity: { value: d.steps, unit: '{steps}', system: 'http://unitsofmeasure.org', code: '{steps}' },
device: { display: 'Suunto watch (Suunto app)' },
}),
});
}
Suunto's API Zone catalogue names the values (HRAvg, HRMin, AvgHRV, StepCount); the full response layout is in its reference, so the examples start from values your code has already read. The Anpheros side is standard FHIR R4, so the same requests work with any FHIR server. To avoid copies when a day is read again, give each Observation a stable identifier, as shown in Wearable data in FHIR.
Ways to get Suunto data into FHIR
| Path | Status | What you need |
|---|---|---|
| Your backend reads the Suunto Cloud API, then writes FHIR to Anpheros | Works today | Acceptance into the Suunto partner programme, a signed API agreement, an API Zone subscription key and the person's OAuth consent |
| Suunto app → Apple Health → your iPhone app reads HealthKit → Anpheros | Works today | HealthKit permissions, with Apple Health switched on in the Suunto app. Suunto does not list the data types. See Apple Health to FHIR |
| Suunto app → Health Connect → your Android app → Anpheros | Not documented by Suunto | See Health Connect to FHIR for the Android side |
| A direct Suunto connector inside Anpheros | On our roadmap | Not available yet. Use the button below to tell us you need it |
The Suunto API in short
Access. Apply to the Suunto partner programme ([email protected]). It is for companies and organisations building commercial or non-commercial apps, not for personal use. Choosing "Cloud API" in the application leads to an API agreement, and Suunto answers within about two weeks if you are accepted. You then subscribe to the rate-limited "Developer API" product in API Zone, which runs on production data; to go live you subscribe to the Production API and Suunto contacts you. One API Zone account holds one app, so test and production apps need separate emails. Suunto does not state a price.
Auth. OAuth 2.0 authorization code flow on https://cloudapi-oauth.suunto.com (/oauth/authorize, /oauth/token, /oauth/deauthorize); the token exchange uses HTTP Basic with your client ID and secret. The access token is a JWT; the docs' sample lasts 24 hours, comes with a refresh token and the scope workout, and its user claim holds the Suunto username you use to match webhooks. Every Cloud API call needs Authorization: Bearer <JWT> and an Ocp-Apim-Subscription-Key header (or a subscription-key query parameter); the OAuth calls do not.
| Data | Endpoint (GET, base https://cloudapi.suunto.com) |
Fields |
|---|---|---|
| Sleep | /247samples/sleep?from=&to= (epoch ms, up to 28 days) |
deep, light and REM durations, HRAvg, HRMin, AvgHRV, MaxSpo2, sleep quality score, nap flag |
| Heart rate, HRV, steps, SpO₂ | /247samples/activity?from=&to= (up to 28 days) |
HR, HR minimum and maximum, HRV, StepCount, EnergyConsumption, SpO2 |
| Recovery | /247samples/recovery?from=&to= |
Balance, StressState |
| Daily totals | /247samples/daily-activity-statistics?startdate=&enddate= |
daily statistics |
| Workouts | /v3/workouts, /v3/workouts/{workoutKey}, /v3/workouts/{workoutIdOrKey}/fit |
summary and FIT with GPS, heart rate and laps |
| Also | routes /v2/route, POIs /v2/poi, upload /v2/upload, SuuntoPlus Guides /v2/guides |
The Developer API allows 10 calls per minute and up to 200 per week. Production limits are not published, and the API answers 503 when service capacity is exceeded. Webhooks cover new workouts, new routes, 24/7 activity, sleep and recovery, signed with a notification secret. Suunto's FAQ still says sleep is not available, but the live API catalogue and the webhooks page list it. The old Workout API at /v2/workouts is deprecated in favour of /v3/workouts, and the 247 data API adds sleep, recovery and continuous heart rate. Suunto China uses separate endpoints, on request.
Sources: Suunto API Zone FAQ, how to start, webhooks, API products, 247 data API operations, Suunto partners, Suunto app on the App Store, November 2026.
Suunto data as FHIR Observations
| Suunto data | LOINC | UCUM unit | Note |
|---|---|---|---|
| Sleep duration | 93832-4 | min |
one per night |
| Deep, REM, light sleep | 93831-6, 93829-0, 93830-8 | min |
components or separate Observations |
Steps (StepCount, daily statistics) |
41950-7 | {steps} |
one per day |
Heart rate (HRAvg, 24/7 HR) |
8867-4 | /min |
daily or nightly average, with minimum and maximum as components |
SpO₂ (SpO2, MaxSpo2) |
59408-5 | % |
say in the note when the value is a night maximum |
HRV (AvgHRV, HRV) |
see note | ms |
Suunto does not name the statistic in the catalogue; do not file it under LOINC 80404-7 unless it is SDNN. See Wearable data in FHIR |
| Recovery balance, stress state | none | Suunto scores with no LOINC code; keep them under your own code system or leave them out |
Frequently asked questions
How do I get access to the Suunto API?
Apply to the Suunto partner programme and choose "Cloud API". If Suunto accepts you, you sign an API agreement and subscribe to the Developer API in API Zone. Suunto answers within about two weeks.
Is the Suunto API free?
Suunto does not publish a price. Access goes through the partner programme and an API agreement.
Can I use the Suunto API for my own data?
No. Suunto says the partner programme is for companies and organisations building apps, not for personal use.
What is the Suunto API rate limit?
The Developer API allows 10 calls per minute and up to 200 per week. Production limits are not published; the API answers 503 when capacity is exceeded.
What is the Ocp-Apim-Subscription-Key in the Suunto API?
The subscription key from your API Zone profile. Send it on every Cloud API call next to the Bearer token, as a header or as the subscription-key query parameter.
Does Anpheros connect to Suunto directly?
Not yet. Today your code reads the Suunto API (or HealthKit on the iPhone) and writes standard FHIR to Anpheros. A direct Suunto connector is on our roadmap; tell us with the button on this page if you need it.
Related
- Wearable APIs: every brand compared
- Wearable data in FHIR (Apple Health, Health Connect)
- Apple Health (HealthKit) to FHIR
- Health Connect to FHIR
- Polar AccessLink API to FHIR · COROS API to FHIR · Garmin API to FHIR
Ask your AI
Paste this into Claude, ChatGPT, Cursor or Claude Code. Your assistant reads this guide and adapts it to your code.
Read https://developers.anpheros.com/guides/suunto-api.md and help me apply this guide to my own project. Use the Anpheros sandbox (base URL https://platform.anpheros.com) with my API key from the ANPHEROS_KEY environment variable, never written in the code. Give me complete, runnable code, the response I should expect, and how to fix the errors in https://developers.anpheros.com/guides/troubleshooting.md if a call fails.Open in ClaudeOpen in ChatGPTThis guide as MarkdownBuild the whole integration with an AI coding tool
Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no cardYour first API call in 2 minutes →