Plans
A plan is a multi-week program: weeks contain days, and each non-rest day points at a workout.
List plans
1GET /api/plans/client| Parameter | Type | Default | Matches |
search | string | none | Partial, case-insensitive match on the English title |
level | int | none | Exact match on the plan's numeric level. A non-integer returns 400 |
body_parts | string[] | none | Body part name, case-insensitive |
translation_languages | string[] | none | Only plans translated into one of these language codes |
include_weeks | bool | false | true expands each plan with its weeks, days, and per-day workouts |
include_shared_library | bool | true | false returns only your company's own plans |
lang | string | en | Language of the returned text |
limit | int | 10 | Page size, clamped to 100 |
offset | int | 0 | Rows 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.
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"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}data, not plans. Exercises use exercises and workouts use workouts.Plan fields (list view)
| Field | Type | Description |
id | number | Plan identifier |
img_url | string | Cover image |
level | number | Numeric level, the value ?level= matches against |
translation | object | Localized title and description, falls back to English |
translation_languages | string[] | Language codes loaded for this plan |
weeks_count | number | Number of weeks |
workout_count | number | Number of workout days across the plan |
body_parts | string[] | Targeted body parts |
category_levels | object[] | { "name", "score" } per training category |
created_by | object | { "id": <company id> } |
created_at / updated_at | string | ISO 8601 timestamps |
weeks | array | Present 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).
translation_languages on this endpoint reports the language you asked for plus English. Use translation_languages=<code> as a filter to test coverage accurately.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
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.
1curl "https://data.kinestex.com/api/plans/client/45" \
2 -H "x-api-key: YOUR_API_KEY" \
3 -H "Language: en"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)
| Field | Type | Description |
id | string | Plan identifier, a string here and a number in the list |
level | string | The 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_active | bool | Catalog visibility of the plan itself, unrelated to user progress |
title / description | string | Localized, at the root rather than under translation |
currentWorkout | object or null | The next workout to play, as a full workout object with its sequence, identical in shape to GET /api/workouts/client/{id} |
body_parts | string[] | Targeted body parts |
category_levels | object[] | { "name", "score" } per training category |
weeks | object[] | Weeks with nested days, see below |
Week object
| Field | Type | Description |
week_number | number | 1-indexed position |
title / description | string | Localized, also mirrored under translation |
intensity | number or null | Relative intensity of the week |
rest_multiplier | number or null | Rest-period multiplier applied to this week |
isActive | bool | This is the week the user is on |
isComplete | bool | Every workout day in the week is completed |
days | object[] | Days in ascending day_number |
Day object
| Field | Type | Description |
day_number | number | 1-indexed position within the week |
is_rest | bool | Rest day, workout is null |
title | string | Localized day title |
isCompleted | bool | The user has completed this day |
isActive | bool | This is the day the user is on |
workout | object or null | Summary: id, title, description, img_url, calories, total_minutes |
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:
1{
2 "error": "Plan not found",
3 "message": "No plan found matching '4-Week Strength Plan'"
4}