Content API
The KinesteX Content API provides access to workout plans, individual workouts, and exercises. Use it to build custom content browsers, create personalized workout recommendations, or integrate KinesteX content into your app.
SDK vs REST API:
- Swift, Kotlin, Flutter: Use the built-in SDK convenience methods (recommended)
- React Native, React TypeScript, HTML/JS: Use direct REST API calls
Base URL: `https://admin.kinestex.com/api/v1/`
Available Endpoints:
| Endpoint | Description |
| /workouts | Fetch workout content |
| /plans | Fetch workout plan content |
| /exercises | Fetch exercise content |
Headers (REST API only):
| Header | Description |
| x-api-key | Your API key for authentication |
| x-company-name | The name of your company |
Getting Started
Before using the Content API, ensure your SDK is properly initialized. The API methods are only available after initialization.
For SDK platforms (Swift, Kotlin, Flutter): Initialize the SDK with your credentials
For REST platforms (React Native, React TypeScript, HTML/JS): Set up your request headers
1import KinesteXAIKit
2
3// Initialize KinesteXAIKit with your credentials
4let kinestex = KinesteXAIKit(
5 apiKey: "YOUR_API_KEY",
6 companyName: "YOUR_COMPANY",
7 userId: "user_123"
8)
9
10// Now you can use the Content API methods:
11// - kinestex.fetchWorkouts()
12// - kinestex.fetchPlans()
13// - kinestex.fetchExercises()
14// - kinestex.fetchWorkout(id:)
15// - kinestex.fetchPlan(id:)
16// - kinestex.fetchExercise(id:)
17// - kinestex.fetchContent(contentType:, ...)Available SDK Methods
KinesteXAIKit provides convenient methods that handle all the complexity of API calls for you.
1// Fetch lists with optional filters
2func fetchWorkouts(category: String? = nil, bodyParts: [BodyPart]? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result<WorkoutsResponse, Error>
3
4func fetchExercises(bodyParts: [BodyPart]? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result<ExercisesResponse, Error>
5
6func fetchPlans(category: String? = nil, limit: Int? = 10, lastDocId: String? = nil, lang: String = "en") async -> Result<PlansResponse, Error>
7
8// Fetch single items by ID
9func fetchWorkout(id: String, lang: String = "en") async -> Result<WorkoutModel, Error>
10func fetchExercise(id: String, lang: String = "en") async -> Result<ExerciseModel, Error>
11func fetchPlan(id: String, lang: String = "en") async -> Result<PlanModel, Error>1// Use fetchContent for advanced filtering or when you need the raw result type
2func fetchContent(
3 contentType: ContentType, // .workout, .plan, .exercise
4 id: String? = nil,
5 title: String? = nil,
6 lang: String = "en",
7 category: String? = nil,
8 bodyParts: [BodyPart]? = nil,
9 lastDocId: String? = nil,
10 limit: Int? = nil
11) async -> APIContentResultFetching Content Lists
Fetch lists of workouts, plans, or exercises with optional filtering.
Parameters:
| Parameter | Type | Description |
| category | String | Filter by category (optional) |
| bodyParts | [BodyPart] | Filter by targeted body parts (optional) |
| limit | Int | Number of results to return (default: 10) |
| lang | String | Language code (default: "en") |
| lastDocId | String | For pagination (optional) |
1// Fetch workouts with optional filters
2Task {
3 let result = await kinestex.fetchWorkouts(
4 category: "Fitness", // or "Rehabilitation"
5 limit: 10
6 )
7
8 switch result {
9 case .success(let response):
10 let workouts = response.workouts
11 print("Fetched \(workouts.count) workouts")
12
13 for workout in workouts {
14 print("- \(workout.title): \(workout.totalMinutes ?? 0) mins")
15 }
16
17 // Store lastDocId for pagination
18 let nextPageId = response.lastDocId
19
20 case .failure(let error):
21 print("Error: \(error.localizedDescription)")
22 }
23}1// Fetch workout plans
2Task {
3 let result = await kinestex.fetchPlans(
4 category: "Strength", // Rehabilitation, Weight Management, Cardio, Strength
5 limit: 5
6 )
7
8 switch result {
9 case .success(let response):
10 let plans = response.plans
11 print("Fetched \(plans.count) plans")
12
13 case .failure(let error):
14 print("Error: \(error.localizedDescription)")
15 }
16}1// Fetch exercises filtered by body parts
2Task {
3 let result = await kinestex.fetchExercises(
4 bodyParts: [.abs, .glutes],
5 limit: 10
6 )
7
8 switch result {
9 case .success(let response):
10 let exercises = response.exercises
11 print("Fetched \(exercises.count) exercises")
12
13 for exercise in exercises {
14 print("- \(exercise.title): \(exercise.bodyParts.joined(separator: ", "))")
15 }
16
17 case .failure(let error):
18 print("Error: \(error.localizedDescription)")
19 }
20}Fetching Single Items
Fetch a specific workout, plan, or exercise by ID or title.
By ID: Use the unique document ID for exact match
By Title: Use the content title (case-insensitive, returns first match)
1// Fetch a specific workout by ID
2Task {
3 let result = await kinestex.fetchWorkout(id: "9zE1kzOzpU5d5dAJrPOY")
4
5 switch result {
6 case .success(let workout):
7 print("Workout: \(workout.title)")
8 print("Duration: \(workout.totalMinutes ?? 0) minutes")
9 print("Calories: \(workout.totalCalories ?? 0)")
10 print("Exercises: \(workout.sequence.count)")
11
12 case .failure(let error):
13 print("Error: \(error.localizedDescription)")
14 }
15}
16
17// Fetch a specific exercise by ID
18Task {
19 let result = await kinestex.fetchExercise(id: "jz73VFlUyZ9nyd64OjRb")
20
21 switch result {
22 case .success(let exercise):
23 print("Exercise: \(exercise.title)")
24 print("Model ID: \(exercise.modelId)")
25
26 case .failure(let error):
27 print("Error: \(error.localizedDescription)")
28 }
29}
30
31// Fetch a specific plan by ID
32Task {
33 let result = await kinestex.fetchPlan(id: "22B3qRU2r75hVXHgGiGx")
34
35 switch result {
36 case .success(let plan):
37 print("Plan: \(plan.title)")
38
39 case .failure(let error):
40 print("Error: \(error.localizedDescription)")
41 }
42}1// Fetch content by title (returns first match)
2Task {
3 let result = await kinestex.fetchContent(
4 contentType: .workout,
5 title: "Fitness Lite"
6 )
7
8 switch result {
9 case .workout(let workout):
10 print("Found workout: \(workout.title)")
11
12 case .error(let message):
13 print("Error: \(message)")
14
15 default:
16 print("Unexpected result type")
17 }
18}Filtering & Parameters
Filter content by category and body parts for targeted results.
Workout Categories: Fitness, Rehabilitation
Plan Categories: Rehabilitation, Weight Management, Cardio, Strength
Body Parts (BodyPart enum):
| SDK Value (Swift) | SDK Value (Kotlin) | SDK Value (Flutter) | REST API Value |
| .abs | ABS | BodyPart.abs | Abs |
| .biceps | BICEPS | BodyPart.biceps | Biceps |
| .calves | CALVES | BodyPart.calves | Calves |
| .chest | CHEST | BodyPart.chest | Chest |
| .externalOblique | EXTERNAL_OBLIQUE | BodyPart.externalOblique | External Oblique |
| .forearms | FOREARMS | BodyPart.forearms | Forearms |
| .glutes | GLUTES | BodyPart.glutes | Glutes |
| .hamstrings | HAMSTRINGS | BodyPart.hamstrings | Hamstrings |
| .lats | LATS | BodyPart.lats | Lats |
| .lowerBack | LOWER_BACK | BodyPart.lowerBack | Lower Back |
| .neck | NECK | BodyPart.neck | Neck |
| .quads | QUADS | BodyPart.quads | Quads |
| .shoulders | SHOULDERS | BodyPart.shoulders | Shoulders |
| .traps | TRAPS | BodyPart.traps | Traps |
| .triceps | TRICEPS | BodyPart.triceps | Triceps |
| .fullBody | FULL_BODY | BodyPart.fullBody | Full Body |
1// Combine category and body parts filters
2Task {
3 let result = await kinestex.fetchContent(
4 contentType: .workout,
5 category: "Fitness",
6 bodyParts: [.abs, .glutes, .quads],
7 limit: 10
8 )
9
10 switch result {
11 case .workouts(let response):
12 let workouts = response.workouts
13 print("Found \(workouts.count) workouts targeting abs, glutes, and quads")
14
15 case .error(let message):
16 print("Error: \(message)")
17
18 default:
19 break
20 }
21}Pagination
For large result sets, use pagination with the `lastDocId` parameter to fetch subsequent pages.
How it works:
1. First request: Omit `lastDocId` to get the first page
2. Store the ID: Save the `lastDocId` from the response
3. Next request: Pass the saved ID as `lastDocId` to get the next page
4. Repeat: Continue until no more results are returned
1// Fetch all workouts with pagination
2func fetchAllWorkouts() async throws -> [WorkoutModel] {
3 var allWorkouts: [WorkoutModel] = []
4 var lastDocId: String? = nil
5
6 repeat {
7 let result = await kinestex.fetchWorkouts(
8 category: "Fitness",
9 limit: 10,
10 lastDocId: lastDocId
11 )
12
13 switch result {
14 case .success(let response):
15 allWorkouts.append(contentsOf: response.workouts)
16 lastDocId = response.lastDocId
17
18 // If lastDocId is empty or nil, we've reached the end
19 if lastDocId?.isEmpty ?? true {
20 lastDocId = nil
21 }
22
23 print("Fetched page with \(response.workouts.count) workouts")
24
25 case .failure(let error):
26 throw error
27 }
28 } while lastDocId != nil
29
30 print("Total workouts fetched: \(allWorkouts.count)")
31 return allWorkouts
32}Error Handling
Handle API errors gracefully in your application.
Response Codes:
| Status | Description |
| 200/201 | Request successful |
| 400 | Validation error (check parameters) |
| 401 | Unauthorized (invalid API key) |
| 404 | Content not found |
| 500 | Internal server error |
1// Comprehensive error handling with Swift SDK
2Task {
3 let result = await kinestex.fetchWorkouts(category: "Fitness", limit: 10)
4
5 switch result {
6 case .success(let response):
7 // Handle successful response
8 let workouts = response.workouts
9 print("Success: Fetched \(workouts.count) workouts")
10
11 case .failure(let error):
12 // Handle different error types
13 if let urlError = error as? URLError {
14 switch urlError.code {
15 case .notConnectedToInternet:
16 print("No internet connection")
17 case .timedOut:
18 print("Request timed out")
19 default:
20 print("Network error: \(urlError.localizedDescription)")
21 }
22 } else {
23 print("Error: \(error.localizedDescription)")
24 }
25 }
26}
27
28// Using fetchContent for advanced error handling
29Task {
30 let result = await kinestex.fetchContent(
31 contentType: .workout,
32 id: "invalid_id"
33 )
34
35 switch result {
36 case .workout(let workout):
37 print("Found: \(workout.title)")
38
39 case .error(let message):
40 // API returned an error message
41 print("API Error: \(message)")
42
43 case .rawData(let data, let errorMessage):
44 // Parsing failed, but raw data is available
45 print("Parse error: \(errorMessage ?? "Unknown")")
46 print("Raw data: \(data)")
47
48 default:
49 print("Unexpected result type")
50 }
51}Data Models
Reference for the data structures returned by the Content API.
WorkoutModel:
| Field | Type | Description |
| id | String | Unique identifier |
| title | String | Workout name |
| category | String | Fitness or Rehabilitation |
| calories | Int? | Estimated calories burned |
| totalMinutes | Int? | Total duration in minutes |
| bodyParts | [String] | Targeted body parts |
| difficultyLevel | String? | Difficulty level |
| description | String | Workout description |
| imgURL | String | Workout thumbnail image |
| sequence | [ExerciseModel] | List of exercises |
ExerciseModel:
| Field | Type | Description |
| id | String | Unique identifier |
| title | String | Exercise name |
| bodyParts | [String] | Targeted body parts |
| videoURL | String | Demo video URL |
| thumbnailURL | String | Thumbnail image URL |
| modelId | String | Motion tracking model ID (use in Camera Component) |
| description | String | Exercise description |
| steps | [String] | Step-by-step instructions |
| commonMistakes | String | Common mistakes to avoid |
| tips | String | Tips for proper form |
PlanModel:
| Field | Type | Description |
| id | String | Unique identifier |
| title | String | Plan name |
| imgURL | String | Plan thumbnail image |
| category | PlanModelCategory | Category with description and levels |
| levels | [String: PlanLevel] | Dictionary of levels (1, 2, 3, etc.) |
| createdBy | String | Creator identifier |
PlanLevel:
| Field | Type | Description |
| title | String | Level title |
| description | String | Level description |
| days | [String: PlanDay] | Dictionary of days |
PlanDay:
| Field | Type | Description |
| title | String | Day title |
| description | String | Day description |
| workouts | [WorkoutSummary]? | List of workouts for this day |
1// Accessing workout model properties
2Task {
3 let result = await kinestex.fetchWorkout(id: "9zE1kzOzpU5d5dAJrPOY")
4
5 switch result {
6 case .success(let workout):
7 // Basic properties
8 print("Title: \(workout.title)")
9 print("Category: \(workout.category ?? "N/A")")
10 print("Duration: \(workout.totalMinutes ?? 0) minutes")
11 print("Calories: \(workout.totalCalories ?? 0)")
12 print("Difficulty: \(workout.difficultyLevel ?? "N/A")")
13
14 // Body parts
15 print("Targets: \(workout.bodyParts.joined(separator: ", "))")
16
17 // Exercise sequence
18 print("\nExercises (\(workout.sequence.count)):")
19 for (index, exercise) in workout.sequence.enumerated() {
20 print("\(index + 1). \(exercise.title)")
21 print(" Model ID: \(exercise.modelId)") // Use for Camera Component
22 print(" Reps: \(exercise.workoutReps ?? exercise.averageReps ?? 0)")
23 }
24
25 // Access raw JSON if needed
26 if let rawJSON = workout.rawJSON {
27 print("\nRaw JSON available: \(rawJSON.keys.count) keys")
28 }
29
30 case .failure(let error):
31 print("Error: \(error.localizedDescription)")
32 }
33}
34
35// Working with plan structure
36Task {
37 let result = await kinestex.fetchPlan(id: "22B3qRU2r75hVXHgGiGx")
38
39 switch result {
40 case .success(let plan):
41 print("Plan: \(plan.title)")
42 print("Category: \(plan.category.description)")
43
44 // Iterate through levels
45 for (levelKey, level) in plan.levels {
46 print("\nLevel \(levelKey): \(level.title)")
47
48 // Iterate through days
49 for (dayKey, day) in level.days {
50 print(" Day \(dayKey): \(day.title)")
51
52 // List workouts for this day
53 if let workouts = day.workouts {
54 for workout in workouts {
55 print(" - \(workout.title) (\(workout.totalMinutes) min)")
56 }
57 }
58 }
59 }
60
61 case .failure(let error):
62 print("Error: \(error.localizedDescription)")
63 }
64}Need Help?
Our team is ready to assist with your integration.

