Guides
Logging workouts
Sending a workout#
curl -X POST "https://dyb5ahy4siv91.cloudfront.net/partner/v1/activities" \
-H "Authorization: Bearer $AG_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"partnerUserId": "emp-1042",
"externalId": "wk-20261014-0007",
"type": "RUNNING",
"startedAt": "2026-10-14T05:30:00Z",
"durationSeconds": 2700,
"distanceMeters": 8200
}'Our user ID, the userId from Add a person. Send exactly one of userId, userEmail and partnerUserId.
The email you added the person with.
Your own ID for the person, as you sent it in Add a person.
Your ID for this workout, up to 128 characters: letters, numbers and - _ . :. It never changes and is also the retry key: the same workout sent twice counts once.
A key from activityTypes in Get your team.
From the start of yesterday, in your team's time zone, until now. The workout must end no more than 5 minutes in the future.
60 to 86,400.
Required when the type has earnsBy: "DISTANCE". Above 0, up to 1,000,000.
Required, above 0, when the type has earnsBy: "CALORIES". Up to 20,000.
- Send exactly one of
userId,userEmailandpartnerUserIdto say whose workout it is. - We take workouts from the start of yesterday, in your team's time zone, until now. Others get
422 started_at_out_of_range. - Each type earns by distance or by calories (
earnsByin Get your team). SenddistanceMetersorkilocaloriesto match. - We do not take heart rate, elevation or GPS routes. They do not change a workout's impact, and they are health and location data we do not want. Any field not listed above gets
400 unknown_field.
Counted or refused#
Every workout gets a response straight away. A 201 means it counted: status is COUNTED and impact says what it earned. A workout that cannot count is refused with an error and not stored, so you can send it again later if the reason goes away.
{
"error": {
"code": "daily_limit_reached",
"message": "The person's daily limit is reached. It resets at error.resetsAt.",
"fields": [],
"resetsAt": "2026-10-14T22:00:00Z"
},
"requestId": "c4c1c7eb-1eef-45c5-9c4b-a5febef121d5"
}| code | Meaning |
|---|---|
409 daily_limit_reached | The person reached today's limit. error.resetsAt says when it resets (UTC). |
409 overlapping_activity | The person already has a workout at the same time, for example the same run from Strava or Apple Health. It counts once. |
A counted workout can still earn less than usual, when only part of it fits under a cap. impact.reducedBy then says which:
| reducedBy | Meaning |
|---|---|
ACTIVITY_CAP | One workout can earn only so much impact. |
TEAM_GOAL | Only part of it fit before the team goal. |
DAILY_IMPACT | Only part of it fit in the person's daily impact limit. |
Retries and duplicates#
Your externalId is the retry key. Sending the same workout again with the same externalId and the same values returns the stored workout with 200 and counts it once. So when a call times out or fails with a 5xx, just send it again.
- Same
externalIdwith different values gets409 duplicate_external_id. A workout cannot be changed once sent. - If the person also tracks with Strava or Apple Health, the same workout can reach us twice. The second one is refused with
409 overlapping_activityand counts once. - Sent the wrong thing? There is no update or delete yet. Contact us with the request ID and we fix it.
Limits#
Workouts follow your team's rules: a daily limit per person, the team's impact goal and the sponsor's budget. Read them, along with places left and the workout types your team takes, from Get your team.