Exercises
An exercise is a single movement: demo video, difficulty, target body parts, coaching tips, and the AI model that scores the user's form.
List exercises
text
1GET /api/exercises/client| Parameter | Type | Default | Matches |
search | string | none | Partial, case-insensitive match on the English title |
body_parts | string[] | none | Body part name, case-insensitive |
categories | string[] | none | Category name, case-insensitive. Deleted categories never match |
difficulty_level | string[] | none | easy, medium, hard. Alias: dif_level. If both are sent, difficulty_level wins |
translation_languages | string[] | none | Only exercises that have a translation in one of these language codes |
remove_inactive | bool | true | false also returns deactivated exercises |
include_shared_library | bool | true | false returns only your company's own exercises |
lang | string | en | Language of the returned text |
limit | int | 10 | Page size, clamped to 100 |
offset | int | 0 | Rows to skip |
bash
1curl "https://data.kinestex.com/api/exercises/client?body_parts=Chest,Triceps&categories=Strength&difficulty_level=easy,medium&lang=es&limit=2" \
2 -H "x-api-key: YOUR_API_KEY"json
1{
2 "exercises": [
3 {
4 "id": 412,
5 "title": "Flexiones",
6 "description": "Ejercicio de empuje para pecho y triceps.",
7 "difficulty_level": "easy",
8 "position": "lying",
9 "calories_per_rep": 0.32,
10 "is_active": true,
11 "body_parts": [{ "name": "Chest" }, { "name": "Triceps" }],
12 "categories": ["Strength", "Muscle Gain"],
13 "contraindications": [],
14 "equipment": [
15 {
16 "id": 3,
17 "title": "Esterilla",
18 "description": "Esterilla de ejercicio estandar.",
19 "thumbnail_url": "https://firebasestorage.googleapis.com/.../mat.png?alt=media"
20 }
21 ],
22 "translation_languages": ["es", "en"],
23 "created_at": "2026-01-14T10:00:00Z",
24 "thumbnail_url": "https://firebasestorage.googleapis.com/.../thumb.png?alt=media",
25 "video_url": "https://firebasestorage.googleapis.com/.../video.mp4?alt=media",
26 "male_thumbnail_url": null,
27 "male_video_url": null,
28 "rest_speech": "descansa-treinta-segundos-abc123",
29 "rest_speech_text": "Descansa 30 segundos",
30 "repeats": 12,
31 "countdown": 3,
32 "correct_second": 1.5,
33 "tips": ["Manten el core activo."],
34 "steps": ["Colocate en posicion de plancha.", "Baja el pecho hacia el suelo."],
35 "common_mistakes": "Dejar caer la cadera.",
36 "created_by": { "id": 1 }
37 }
38 ],
39 "pagination": {
40 "total": 42,
41 "limit": 2,
42 "offset": 0,
43 "total_pages": 21,
44 "current_page": 1,
45 "has_next": true,
46 "has_prev": false
47 }
48}Exercise fields (list view)
| Field | Type | Description |
id | number | Exercise identifier |
title / description | string | Localized text, falls back to English |
difficulty_level | string | easy, medium, or hard |
position | string | standing, sitting, or lying. Returned, not filterable |
calories_per_rep | number | Estimated calories per repetition |
is_active | bool or null | Null is treated as active (legacy rows) |
body_parts | object[] | Array of { "name": "Chest" } objects |
categories | string[] | Flat array of category names |
contraindications | string[] | Body areas where caution is needed |
equipment | object[] | id, title, description, thumbnail_url, localized |
translation_languages | string[] | Language codes available for this exercise |
thumbnail_url / video_url | string | Default (female) demo assets |
male_thumbnail_url / male_video_url | string or null | Male demo assets where produced |
repeats | number or null | Default repetition count |
countdown | number or null | Timer length in seconds, for timer-based exercises |
correct_second | number or null | Seconds one correct repetition should take |
tips | string[] | Coaching tips, localized |
steps | string[] | Step-by-step instructions, localized |
common_mistakes | string | Omitted when empty |
rest_speech / rest_speech_text | string | Rest-period audio identifier and its text |
created_by | object | { "id": <company id> } |
created_at | string | ISO 8601 timestamp |
An exercise matching several of your requested categories or body parts appears once, and
pagination.total counts distinct exercises, so paging never repeats or skips a row.Known quirk: on this endpoint
translation_languages reports only the language you asked for plus English, because only those translation rows are loaded. To test coverage for another language, filter with translation_languages=<code> instead: that filter queries the full translation table and is accurate. The workout list reports the complete set.Get one exercise
text
1GET /api/exercises/client/{id}{id} accepts a numeric ID (412) or a title (Push Ups). Titles are normalized before matching (lowercased, non-alphanumeric characters removed), so Push Ups, push-ups, and pushups all resolve to the same exercise. URL-encode spaces as %20.
| Parameter | Type | Default | Description |
lang | string | en | Language code, overrides the headers |
include_audio | bool | true | false omits the generated audio URLs and returns a lighter payload |
bash
1curl "https://data.kinestex.com/api/exercises/client/Push%20Ups?lang=es" \
2 -H "x-api-key: YOUR_API_KEY"json
1{
2 "id": "412",
3 "ai_model": { "id": "57" },
4 "repeats": 12,
5 "countdown": 3,
6 "thumbnail_url": "https://firebasestorage.googleapis.com/.../thumb.png?alt=media",
7 "video_url": "https://firebasestorage.googleapis.com/.../video.mp4?alt=media",
8 "male_thumbnail_url": null,
9 "male_video_url": null,
10 "difficulty_level": "easy",
11 "position": "lying",
12 "calories_per_rep": 0.32,
13 "body_parts": ["Chest", "Triceps"],
14 "categories": ["Strength", "Muscle Gain"],
15 "contraindications": ["shoulder"],
16 "equipment": [],
17 "is_active": true,
18 "correct_second": 1.5,
19 "non_motivational": false,
20 "translation": {
21 "id": 50,
22 "language": "es",
23 "title": "Flexiones",
24 "description": "Ejercicio de empuje para pecho y triceps.",
25 "tips": ["Manten el core activo."],
26 "exercise_steps": ["Colocate en posicion de plancha.", "Baja el pecho hacia el suelo."],
27 "common_mistakes": "Dejar caer la cadera.",
28 "rest_speech": "descansa-treinta-segundos-abc123",
29 "rest_speech_text": "Descansa 30 segundos",
30 "rest_speech_url_m4a": "https://firebasestorage.googleapis.com/.../rest.m4a?alt=media",
31 "rest_speech_url_webm": "https://firebasestorage.googleapis.com/.../rest.webm?alt=media",
32 "voice_actor": "Glinda"
33 },
34 "created_by": { "id": 1 }
35}Differences from the list view, worth handling explicitly:
idis a string here and a number in the list.body_partsis a flatstring[]here and an array of{name}objects in the list.- Localized text lives under
translationinstead of at the root, and it carries the audio URLs,voice_actor, andexercise_steps(the list calls the same datasteps). ai_modelis reduced to{ "id": ... }. Pass that ID to the SDK when you build a fully custom workout UI.- Deactivated exercises are still fetchable by ID or title. The
remove_inactivefilter applies to the list only.
Not found returns 404:
json
1{
2 "message": "Exercise not found",
3 "details": "exercise with normalized title 'unknown' not found in company or library exercises"
4}