Workout Sessions API
Every completed workout session is stored server-side, tied to the userId the SDK was initialized with (sessions are persisted when the workout is launched with shouldSendStats: true). Fetch the history to build progress screens, completed-workout lists, or your own statistics page.
Two read endpoints share the same authentication:
| Endpoint | Returns |
GET /api/workout-sessions | Paginated session history for a user |
GET /api/workout-sessions/{id} | One session in full detail, including per-exercise results |
Headers (both endpoints):
| Header | Description |
x-api-key | Your company API key (server-side only) |
x-user-id | The user's id — always send it; it must match the SDK's userId |
Optionally pass Language: es (or Accept-Language) to have planned_exercises[].title returned in that language.
List sessions
1GET https://data.kinestex.com/api/workout-sessionsQuery params (all optional): page (default 1), limit (default 20, max 50), sort (default started_at_desc).
Response (trimmed to the most useful fields):
1{
2 "sessions": [
3 {
4 "id": 4978,
5 "content_title": "Fitness Lite",
6 "content_image_url": "https://cdn.kinestex.com/uploads%2F...webp",
7 "content_difficulty": "Medium",
8 "calories_burned": 1.03,
9 "actual_duration_seconds": 12,
10 "completion_percentage": 0.67,
11 "total_exercises": 10
12 }
13 ],
14 "pagination": { "page": 1, "limit": 20, "total": 1, "total_pages": 1, "has_more": false }
15}Each session also carries planned_exercises[] (with per-exercise thumbnails and rep/time targets), accuracy_score, efficiency_score, total_mistakes, and timestamps.
Key rules:
- The values are actuals:
calories_burnedis what the user really burned andactual_duration_secondsis real time spent (in seconds — format it; don't show "0 min" for short sessions). completion_percentage(0–100, can be fractional like0.67) is authoritative — never derive completion from exercise counts, sincetotal_exercisesincludes untouched exercises.- Errors:
401invalidx-api-key·404x-user-idnot found for your company ·400bad query param.
Get a single session
1GET https://data.kinestex.com/api/workout-sessions/{id}Returns one workout session in full detail — including the per-exercise breakdown (exercises[]) that the list endpoint doesn't include. Use it to build your own session-detail or statistics page. {id} is the numeric session id from the list response, or from the SDK's workout_session_saved event (session_id).
1curl "https://data.kinestex.com/api/workout-sessions/5124" \
2 -H "x-api-key: YOUR_API_KEY" \
3 -H "x-user-id: user-abc-123"Response — the session object is returned directly (no wrapper):
1{
2 "id": 5124,
3 "created_at": "2026-08-04T09:47:31Z",
4 "updated_at": "2026-08-04T09:47:31Z",
5 "company_user_id": "user-abc-123",
6 "company_id": 42,
7 "integration_option": "workout",
8 "client_session_id": "9f4b6c2a-1d3e-4c5b-8a7f-2e6d9c0b4a11",
9 "content_id": "261",
10 "content_title": "Full Body Strength",
11 "content_image_url": "https://cdn.kinestex.com/uploads%2Fworkouts%2Ffull-body.webp",
12 "content_difficulty": "Medium",
13 "planned_exercises": [
14 {
15 "exercise_id": "122",
16 "title": "Squats",
17 "thumbnail_url": "https://cdn.kinestex.com/exercises/squat-thumbnail.jpg",
18 "reps_target": 12,
19 "time_target_seconds": null,
20 "exercise_type": "rep",
21 "language": "en"
22 },
23 {
24 "exercise_id": "87",
25 "title": "Plank",
26 "thumbnail_url": "https://cdn.kinestex.com/exercises/plank-thumbnail.jpg",
27 "reps_target": null,
28 "time_target_seconds": 60,
29 "exercise_type": "timer",
30 "language": "en"
31 },
32 {
33 "exercise_id": "95",
34 "title": "Push Ups",
35 "thumbnail_url": "https://cdn.kinestex.com/exercises/push-ups-thumbnail.jpg",
36 "reps_target": 12,
37 "time_target_seconds": null,
38 "exercise_type": "rep",
39 "language": "en"
40 }
41 ],
42 "started_at": "2026-08-04T09:30:00Z",
43 "completed_at": "2026-08-04T09:47:30Z",
44 "actual_duration_seconds": 1050,
45 "target_duration_seconds": 1080,
46 "completed_reps_count": 22,
47 "target_reps_count": 24,
48 "calories_burned": 10.8,
49 "completion_percentage": 94.44,
50 "accuracy_score": 88,
51 "efficiency_score": 84,
52 "total_mistakes": 6,
53 "total_exercises": 3,
54 "actual_hold_time_seconds": 52,
55 "target_hold_time_seconds": 60,
56 "readiness": null,
57 "post_workout_feedback": null,
58 "has_rep_based_exercises": true,
59 "has_ai_model": true,
60 "exercises": [
61 {
62 "id": 18211,
63 "created_at": "2026-08-04T09:47:31Z",
64 "updated_at": "2026-08-04T09:47:31Z",
65 "workout_session_id": 5124,
66 "exercise_index": 0,
67 "exercise_id": 122,
68 "reps_done": 12,
69 "reps_target": 12,
70 "time_spent_seconds": 45,
71 "time_target_seconds": null,
72 "calories": 4.5,
73 "average_accuracy": 91,
74 "rep_accuracy": [95, 88, 92, 90, 94, 89, 93, 91, 90, 92, 88, 90],
75 "mistakes": [{ "mistake": "straighten back", "count": 2 }],
76 "mistake_count": 2,
77 "perfect_position_time": 0,
78 "mistake_time_spent": 0,
79 "has_ai_model": true,
80 "exercise_type": "rep"
81 },
82 {
83 "id": 18212,
84 "created_at": "2026-08-04T09:47:31Z",
85 "updated_at": "2026-08-04T09:47:31Z",
86 "workout_session_id": 5124,
87 "exercise_index": 1,
88 "exercise_id": 87,
89 "reps_done": 0,
90 "reps_target": null,
91 "time_spent_seconds": 60,
92 "time_target_seconds": 60,
93 "calories": 3.2,
94 "average_accuracy": 87,
95 "rep_accuracy": null,
96 "mistakes": [{ "mistake": "hips too low", "count": 1 }],
97 "mistake_count": 1,
98 "perfect_position_time": 52,
99 "mistake_time_spent": 8,
100 "has_ai_model": true,
101 "exercise_type": "timer"
102 },
103 {
104 "id": 18213,
105 "created_at": "2026-08-04T09:47:31Z",
106 "updated_at": "2026-08-04T09:47:31Z",
107 "workout_session_id": 5124,
108 "exercise_index": 2,
109 "exercise_id": 95,
110 "reps_done": 10,
111 "reps_target": 12,
112 "time_spent_seconds": 40,
113 "time_target_seconds": null,
114 "calories": 3.1,
115 "average_accuracy": 85,
116 "rep_accuracy": [88, 84, 86, 85, 87, 83, 85, 84, 86, 82],
117 "mistakes": [{ "mistake": "elbows flaring", "count": 3 }],
118 "mistake_count": 3,
119 "perfect_position_time": 0,
120 "mistake_time_spent": 0,
121 "has_ai_model": true,
122 "exercise_type": "rep"
123 }
124 ]
125}Session fields:
| Field | Type | Description | |||
id | int | Session id | |||
created_at / updated_at | timestamp | When the record was saved / last updated (ISO 8601) | |||
company_user_id | string | The user's id — same value as x-user-id | |||
company_id | int | Internal id of your company | |||
integration_option | string | Which integration surface launched the workout ("workout" by default) | |||
client_session_id | string \ | null | SDK-generated idempotency key for the save; null on sessions from older SDKs | ||
content_id / content_title / content_image_url / content_difficulty | string \ | null | The workout content that was played | ||
planned_exercises | object[] \ | null | The workout plan as launched — per exercise: exercise_id (string), title, thumbnail_url, reps_target, time_target_seconds, exercise_type ("rep" \ | "timer" \ | "none"), language |
started_at / completed_at | timestamp \ | null | Session start / completion time | ||
actual_duration_seconds / target_duration_seconds | int | Real wall-clock time spent vs. planned duration, in seconds | |||
completed_reps_count / target_reps_count | int | Total reps done vs. planned across rep-based exercises | |||
calories_burned | float | Actual calories burned | |||
completion_percentage | float | 0–100, can be fractional | |||
accuracy_score / efficiency_score | int | 0–100 | |||
total_mistakes | int | Form mistakes across all exercises | |||
total_exercises | int | Planned exercise count (includes untouched exercises) | |||
actual_hold_time_seconds / target_hold_time_seconds | int | Time held in correct position vs. target, for timer-based exercises | |||
readiness | object \ | null | Free-form pre-workout readiness answers, when collected | ||
post_workout_feedback | object \ | null | Free-form post-workout feedback, when collected | ||
has_rep_based_exercises / has_ai_model | bool | Whether the session contained rep-based exercises / AI-tracked exercises | |||
ai_summary | string | AI-generated session summary — present only when one has been generated; omitted otherwise | |||
exercises | object[] | Per-exercise results (see below) |
Exercise entry fields (`exercises[]`):
| Field | Type | Description | ||
id | int | Entry id | ||
workout_session_id | int | Parent session id | ||
exercise_index | int | 0-based position in the workout | ||
exercise_id | int \ | null | Exercise library id (numeric here; planned_exercises[].exercise_id is a string) | |
reps_done | int | Reps completed | ||
reps_target | int \ | null | null for timer-based exercises | |
time_spent_seconds | int | Active time on this exercise | ||
time_target_seconds | int \ | null | null for rep-based exercises | |
calories | float | Calories burned on this exercise | ||
average_accuracy | int | 0–100; 0 when AI tracking was off | ||
rep_accuracy | int[] \ | null | Per-rep accuracy scores (rep-based, AI-tracked exercises) | |
mistakes | object[] \ | null | [{ "mistake": "straighten back", "count": 2 }] | |
mistake_count | int | Total mistake occurrences | ||
perfect_position_time / mistake_time_spent | int | Seconds in correct / incorrect form (timer-based exercises) | ||
has_ai_model | bool | AI form tracking was active for this exercise | ||
exercise_type | string | "rep" \ | "timer" \ | "none" |
Key rules:
- Rep vs. timer: for
"rep"exercises readreps_done/reps_targetandrep_accuracy; for"timer"exercises readtime_spent_seconds/time_target_secondsandperfect_position_time. planned_exercisesis what was planned,exercisesis what actually happened — an abandoned session has fewerexercises[]entries thanplanned_exercises[]. Match them by order (exercise_index).- Sessions are user-scoped: requesting a session id that belongs to a different user returns
403. - The response may include additional internal fields not listed here — ignore unrecognized fields.
- Errors:
400non-numeric{id}·401invalidx-api-key·403session belongs to a different user ·404unknown session id, orx-user-idnot found for your company.
Alternatively, to open a full session summary in your app without building a page, pass the session id to the SDK's custom component view with route session/{id}. The complete card-building walkthrough (Swift + Kotlin) is in Displaying completed workouts.