API reference
Athlete data endpoints
What your app reads with an athlete's access token. Field meanings are in the data model.
Get the athlete's subject
GET/v1/profile
Returns sub, this athlete's ID for your app. Every app sees a different sub for the same athlete, and it stays the same if they revoke and approve again. No email or name.
- Auth
- Bearer access token with
profile:read - Host
- Portal origin or API host
Response 200
Pairwise profile subject.
| Field | Type | Notes |
|---|---|---|
sub required | string | ccu_ and 32 hex characters: this athlete's ID for your app. |
Errors
| Status | Code | When |
|---|---|---|
| 401 | invalid_ | The Bearer token is missing, malformed, expired or revoked. |
| 403 | insufficient_ | The token does not carry the scope this endpoint needs. |
| 429 | rate_ | Your app, or this athlete within your app, used its request limit. |
| 429 | service_ | All third-party apps together reached the shared ceiling. |
curl "$CORRCONNECT_BASE/v1/profile" \
-H "Authorization: Bearer $ACCESS_TOKEN"const response = await fetch(`${process.env.CORRCONNECT_BASE}/v1/profile`, {
headers: {
"Authorization": `Bearer ${process.env.ACCESS_TOKEN}`,
},
});
console.log(response.status, await response.json());import os
import requests
response = requests.get(
f"{os.environ['CORRCONNECT_BASE']}/v1/profile",
headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
timeout=10,
)
print(response.status_code, response.json())import Foundation
let env = ProcessInfo.processInfo.environment
var request = URLRequest(url: URL(string: "\(env["CORRCONNECT_BASE"]!)/v1/profile")!)
request.setValue("Bearer \(env["ACCESS_TOKEN"]!)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self)){
"sub": "ccu_0123456789abcdef0123456789abcdef"
}List activities
GET/v1/activities
The athlete's activities, newest first by when CorrConnect stored them. Pass next_cursor back as cursor for the next page; null means you have them all.
- Auth
- Bearer access token with
activities:read - Host
- Portal origin or API host
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
limit | query | integer | How many records to return. 1 to 100 Default 25 |
cursor | query | string or null | The next_cursor from the previous page. Never build or edit one. |
source | query | string | Only records from this source. One of manual_json, gpx, tcx, fit |
Response 200
Normalized activity page.
| Field | Type | Notes |
|---|---|---|
data required | array of object | |
data[].id required | string | CorrConnect activity ID (act_ and 32 hex characters). |
data[].source required | string | Where the record came from. Treat values you do not know as other sources. One of manual_json, gpx, tcx, fit |
data[]. required | string | Normalized sport. One of run, ride, swim, walk, strength, other |
data[]. required | string (date-time) | Start time with its UTC offset. |
data[]. required | number | Duration in seconds; duration_basis says how it was measured when the source tells us. |
data[]. | number or null | Distance in meters, or absent or null when the source has none. |
data[].title | string or null | Name the athlete or file gave it. |
data[]. | string | How duration_seconds was measured, for example recorded_lap_sum. |
data[]. | string | How distance_meters was measured. |
data[]. | object | File format and position, for example {"format": "FIT", "session_index": 1}. |
data[].segments | array of object | GPX segments: point counts, start and end times, elapsed seconds. |
data[].laps | array of object | TCX or FIT laps: start, duration and distance. Never raw GPS points. |
data[]. required | string (date-time) | When CorrConnect stored it. Lists sort on this, newest first. |
next_cursor required | string or null |
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_source | source is not one of the listed values. |
| 400 | invalid_limit | limit is outside 1 to 100. |
| 400 | invalid_cursor | cursor is not a next_cursor value CorrConnect returned. |
| 401 | invalid_ | The Bearer token is missing, malformed, expired or revoked. |
| 403 | insufficient_ | The token does not carry the scope this endpoint needs. |
| 422 | validation_ | A field is missing, has the wrong type or is out of range; details.fields says which. |
| 429 | rate_ | Your app, or this athlete within your app, used its request limit. |
| 429 | service_ | All third-party apps together reached the shared ceiling. |
curl "$CORRCONNECT_BASE/v1/activities?limit=25" \
-H "Authorization: Bearer $ACCESS_TOKEN"const response = await fetch(`${process.env.CORRCONNECT_BASE}/v1/activities?limit=25`, {
headers: {
"Authorization": `Bearer ${process.env.ACCESS_TOKEN}`,
},
});
console.log(response.status, await response.json());import os
import requests
response = requests.get(
f"{os.environ['CORRCONNECT_BASE']}/v1/activities?limit=25",
headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
timeout=10,
)
print(response.status_code, response.json())import Foundation
let env = ProcessInfo.processInfo.environment
var request = URLRequest(url: URL(string: "\(env["CORRCONNECT_BASE"]!)/v1/activities?limit=25")!)
request.setValue("Bearer \(env["ACCESS_TOKEN"]!)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self)){
"data": [
{
"id": "act_0123456789abcdef0123456789abcdef",
"source": "manual_json",
"activity_type": "run",
"started_at": "2026-09-22T11:15:00+00:00",
"duration_seconds": 2400,
"distance_meters": 8046.7,
"created_at": "2026-09-22T11:16:00+00:00"
}
],
"next_cursor": null
}Get one activity
GET/v1/
One activity belonging to the token's athlete, in the same shape as list items. Use it with the activity_id from an activity.created event.
- Auth
- Bearer access token with
activities:read - Host
- Portal origin or API host
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
activity_id required | path | string |
Response 200
Normalized activity summary.
| Field | Type | Notes |
|---|---|---|
id required | string | CorrConnect activity ID (act_ and 32 hex characters). |
source required | string | Where the record came from. Treat values you do not know as other sources. One of manual_json, gpx, tcx, fit |
activity_type required | string | Normalized sport. One of run, ride, swim, walk, strength, other |
started_at required | string (date-time) | Start time with its UTC offset. |
duration_seconds required | number | Duration in seconds; duration_basis says how it was measured when the source tells us. |
distance_meters | number or null | Distance in meters, or absent or null when the source has none. |
title | string or null | Name the athlete or file gave it. |
duration_basis | string | How duration_seconds was measured, for example recorded_lap_sum. |
distance_basis | string | How distance_meters was measured. |
provenance | object | File format and position, for example {"format": "FIT", "session_index": 1}. |
segments | array of object | GPX segments: point counts, start and end times, elapsed seconds. |
laps | array of object | TCX or FIT laps: start, duration and distance. Never raw GPS points. |
created_at required | string (date-time) | When CorrConnect stored it. Lists sort on this, newest first. |
Errors
| Status | Code | When |
|---|---|---|
| 401 | invalid_ | The Bearer token is missing, malformed, expired or revoked. |
| 403 | insufficient_ | The token does not carry the scope this endpoint needs. |
| 404 | activity_ | No activity with this ID belongs to the token's athlete. |
| 429 | rate_ | Your app, or this athlete within your app, used its request limit. |
| 429 | service_ | All third-party apps together reached the shared ceiling. |
curl "$CORRCONNECT_BASE/v1/activities/$ACTIVITY_ID" \
-H "Authorization: Bearer $ACCESS_TOKEN"const response = await fetch(`${process.env.CORRCONNECT_BASE}/v1/activities/${process.env.ACTIVITY_ID}`, {
headers: {
"Authorization": `Bearer ${process.env.ACCESS_TOKEN}`,
},
});
console.log(response.status, await response.json());import os
import requests
response = requests.get(
f"{os.environ['CORRCONNECT_BASE']}/v1/activities/{os.environ['ACTIVITY_ID']}",
headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}"},
timeout=10,
)
print(response.status_code, response.json())import Foundation
let env = ProcessInfo.processInfo.environment
var request = URLRequest(url: URL(string: "\(env["CORRCONNECT_BASE"]!)/v1/activities/\(env["ACTIVITY_ID"]!)")!)
request.setValue("Bearer \(env["ACCESS_TOKEN"]!)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
print((response as! HTTPURLResponse).statusCode, String(decoding: data, as: UTF8.self)){
"id": "act_0123456789abcdef0123456789abcdef",
"source": "fit",
"activity_type": "ride",
"started_at": "2026-09-22T11:15:00+00:00",
"duration_seconds": 3600,
"created_at": "2026-09-22T11:16:00+00:00"
}