KinesteX

Plans

A plan is a multi-week program: weeks contain days, and each non-rest day points at a workout.




List plans


text
1GET /api/plans/client

ParameterTypeDefaultMatches
searchstringnonePartial, case-insensitive match on the English title
levelintnoneExact match on the plan's numeric level. A non-integer returns 400
body_partsstring[]noneBody part name, case-insensitive
translation_languagesstring[]noneOnly plans translated into one of these language codes
include_weeksboolfalsetrue expands each plan with its weeks, days, and per-day workouts
include_shared_librarybooltruefalse returns only your company's own plans
langstringenLanguage of the returned text
limitint10Page size, clamped to 100
offsetint0Rows to skip

Read the level field off an unfiltered page to see which levels your library actually uses. There is no categories filter here; each plan instead reports its own category_levels scores. Personal (AI-generated) plans are always excluded from this list.


bash
1curl "https://data.kinestex.com/api/plans/client?level=2&body_parts=Full%20Body&limit=2" \
2  -H "x-api-key: YOUR_API_KEY" \
3  -H "Language: en"

json
1{
2  "data": [
3    {
4      "id": 45,
5      "img_url": "https://firebasestorage.googleapis.com/.../cover.png?alt=media",
6      "level": 2,
7      "created_at": "2026-01-14T10:00:00Z",
8      "updated_at": "2026-02-02T08:30:00Z",
9      "translation": {
10        "title": "Four Week Reset",
11        "description": "A four-week full-body progression."
12      },
13      "translation_languages": ["en", "es"],
14      "weeks_count": 4,
15      "workout_count": 20,
16      "body_parts": ["Full Body"],
17      "category_levels": [
18        { "name": "Strength", "score": 7 },
19        { "name": "Cardio/Endurance", "score": 4 }
20      ],
21      "created_by": { "id": 1 }
22    }
23  ],
24  "pagination": {
25    "total": 6,
26    "limit": 2,
27    "offset": 0,
28    "total_pages": 3,
29    "current_page": 1,
30    "has_next": true,
31    "has_prev": false
32  }
33}

The plan list is keyed differently. Plans arrive under data, not plans. Exercises use exercises and workouts use workouts.

Plan fields (list view)


FieldTypeDescription
idnumberPlan identifier
img_urlstringCover image
levelnumberNumeric level, the value ?level= matches against
translationobjectLocalized title and description, falls back to English
translation_languagesstring[]Language codes loaded for this plan
weeks_countnumberNumber of weeks
workout_countnumberNumber of workout days across the plan
body_partsstring[]Targeted body parts
category_levelsobject[]{ "name", "score" } per training category
created_byobject{ "id": <company id> }
created_at / updated_atstringISO 8601 timestamps
weeksarrayPresent only with include_weeks=true

With include_weeks=true, each plan gains a weeks array of { week_number, title, description, days[] }, and each day is { day_number, title, is_rest, is_done, workout }. is_done is always false in list view (no progression lookup is performed).


Same caveat as exercises: translation_languages on this endpoint reports the language you asked for plus English. Use translation_languages=<code> as a filter to test coverage accurately.

Known quirk: inside include_weeks=true, days[].workout.total_minutes currently carries the workout's duration in seconds, not minutes. Divide by 60, or read total_time and total_minutes from GET /api/workouts/client/{id}, which are correct.



Get one plan


text
1GET /api/plans/client/{id}

{id} accepts a numeric ID (45), a legacy Firestore ID, or a title (Four Week Reset). Titles are normalized before matching. URL-encode spaces as %20.


This endpoint takes no query parameters. In particular it ignores ?lang=: set the Language or Accept-Language header instead. Weeks, days, and the current workout are always included.


bash
1curl "https://data.kinestex.com/api/plans/client/45" \
2  -H "x-api-key: YOUR_API_KEY" \
3  -H "Language: en"

json
1{
2  "id": "45",
3  "img_url": "https://firebasestorage.googleapis.com/.../cover.png?alt=media",
4  "level": "Strength",
5  "is_active": true,
6  "created_at": "2026-01-14T10:00:00Z",
7  "updated_at": "2026-02-02T08:30:00Z",
8  "title": "Four Week Reset",
9  "description": "A four-week full-body progression.",
10  "body_parts": ["Full Body"],
11  "category_levels": [
12    { "name": "Strength", "score": 7 },
13    { "name": "Cardio/Endurance", "score": 4 }
14  ],
15  "currentWorkout": {
16    "id": 88,
17    "category": "Strength",
18    "calories": 250,
19    "dif_level": "Easy",
20    "total_time": 1800,
21    "total_minutes": 30,
22    "translation": { "title": "Upper Body Starter", "language": "en", "description": "A short push-focused session." },
23    "workout_sequences": [...]
24  },
25  "weeks": [
26    {
27      "id": 30,
28      "week_number": 1,
29      "title": "Foundation Week",
30      "description": "Build your base strength.",
31      "intensity": 3,
32      "rest_multiplier": 1,
33      "isActive": true,
34      "isComplete": false,
35      "translation": { "id": 100, "language": "en", "title": "Foundation Week", "description": "Build your base strength." },
36      "days": [
37        {
38          "id": 100,
39          "day_number": 1,
40          "is_rest": false,
41          "title": "Day 1",
42          "isCompleted": false,
43          "isActive": true,
44          "translation": { "id": 200, "language": "en", "title": "Day 1" },
45          "workout": {
46            "id": "88",
47            "img_url": "https://firebasestorage.googleapis.com/.../desc.png?alt=media",
48            "title": "Upper Body Starter",
49            "description": "A short push-focused session.",
50            "calories": 250,
51            "total_minutes": 30
52          }
53        },
54        {
55          "id": 101,
56          "day_number": 2,
57          "is_rest": true,
58          "title": "Rest Day",
59          "isCompleted": false,
60          "isActive": false,
61          "translation": { "id": 201, "language": "en", "title": "Rest Day" },
62          "workout": null
63        }
64      ]
65    }
66  ],
67  "created_by": { "id": 1 }
68}

Plan fields (detail view)


FieldTypeDescription
idstringPlan identifier, a string here and a number in the list
levelstringThe name of the highest-scoring category ("Strength", "Cardio", ...), or the numeric level as a string when no category matches. Not the same shape as the list's numeric level
is_activeboolCatalog visibility of the plan itself, unrelated to user progress
title / descriptionstringLocalized, at the root rather than under translation
currentWorkoutobject or nullThe next workout to play, as a full workout object with its sequence, identical in shape to GET /api/workouts/client/{id}
body_partsstring[]Targeted body parts
category_levelsobject[]{ "name", "score" } per training category
weeksobject[]Weeks with nested days, see below

Week object


FieldTypeDescription
week_numbernumber1-indexed position
title / descriptionstringLocalized, also mirrored under translation
intensitynumber or nullRelative intensity of the week
rest_multipliernumber or nullRest-period multiplier applied to this week
isActiveboolThis is the week the user is on
isCompleteboolEvery workout day in the week is completed
daysobject[]Days in ascending day_number

Day object


FieldTypeDescription
day_numbernumber1-indexed position within the week
is_restboolRest day, workout is null
titlestringLocalized day title
isCompletedboolThe user has completed this day
isActiveboolThis is the day the user is on
workoutobject or nullSummary: id, title, description, img_url, calories, total_minutes

Progress flags depend on who is asking. With a user JWT, or an API key plus x-user-id, isActive / isComplete / isCompleted reflect that user's real progression. With a bare API key there is no user, so the plan reads as not started: nothing is completed and the first day of week 1 is reported as active. Treat the flags as presentation state, not as a source of truth for your own analytics.

Not found returns 404:


json
1{
2  "error": "Plan not found",
3  "message": "No plan found matching '4-Week Strength Plan'"
4}