/api/v1/workoutsRetrieve a list of workouts with details
See data structureGenerate credentials, authenticate requests, and understand the workout and exercise data returned by the Lyfta API.
https://my.lyfta.appYour API key provides programmatic access to your Lyfta data. Use the button below to generate an API key.
Important Notes:
Create an API key above. A new key revokes and replaces any previously active key.
Send your key in the authorization header on every request.
Use limit and page to control list responses.
You can make up to 60 requests per minute and 5,000 requests per day. Exceeding these limits will result in a 429 Too Many Requests response.
Base URL: https://my.lyfta.app
/api/v1/workoutsRetrieve a list of workouts with details
See data structure/api/v1/workouts/summaryRetrieve a list of workouts with summary data
See summary data structure/api/v1/exercisesRetrieve a list of the performed exercises
See exercise data structure/api/v1/exercises/librarySearch the Lyfta exercise catalog
See search details/api/v1/exercises/progressRetrieve exercise progress data
See exercise progress data structure/api/v1/collectionsCreate a new program/collection in your library
See request details/api/v1/templatesCreate a workout template and add it to a collection
See request detailsAll requests must include the following header:
Authorization: Bearer YOUR_API_KEY
Use POST /api/v1/collections to create a new program/collection. Send the same Authorization: Bearer YOUR_API_KEY header and a JSON body with Content-Type: application/json.
The request body must include a collection object. Coaches can optionally include client_id at the top level to create the collection in a client's library instead of their own.
Required fields:
collection.title — must be a non-empty string after trimming. Returns "Title is required" if missing or blank.Optional fields:
collection.description — program description.collection.goal — program goal.collection.image — cover image as a base64-encoded string.client_id — coach only. The client's client_id from GET /api/v1/clients. Omit to create in your own library.Creates a collection in the authenticated user's library.
{
"collection": {
"title": "My Program",
"description": "Optional program description",
"goal": "Optional goal, e.g. strength or hypertrophy",
"image": "/9j/4AAQSkZJRg...base64-encoded image data..."
}
}Add client_id to create the collection in an active client's library. Requires a paid Coach or Scale plan and a linked client with status "1".
{
"client_id": "dj152auh",
"collection": {
"title": "Client Program",
"description": "Optional program description",
"goal": "Optional goal",
"image": "/9j/4AAQSkZJRg...base64-encoded image data..."
}
}On success, the API returns the created collection in data, including the new id.
{
"status": true,
"data": {
"id": 12345,
"title": "My Program",
"description": "Optional program description",
"goal": "Optional goal, e.g. strength or hypertrophy",
"image": "https://cdnlyfta.com/images/original/example.jpeg"
}
}Error when title is missing:
{
"status": false,
"message": "Title is required"
}Image handling:
collection.image must be a base64-encoded image string. URLs are not accepted.data.image contains the final hosted URL.collection.image to create a collection without a cover image.import requests
import json
import base64
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
}
with open('cover.jpg', 'rb') as image_file:
image_base64 = base64.b64encode(image_file.read()).decode('utf-8')
payload = {
'collection': {
'title': 'My Program',
'description': 'Optional program description',
'goal': 'strength',
'image': image_base64,
},
}
response = requests.post(
f'{BASE_URL}/api/v1/collections',
headers=headers,
json=payload,
)
if response.status_code == 200:
print(json.dumps(response.json(), indent=2))
else:
print(f"Error {response.status_code}:", response.text)
import requests
import json
import base64
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
}
with open('cover.jpg', 'rb') as image_file:
image_base64 = base64.b64encode(image_file.read()).decode('utf-8')
payload = {
'client_id': 'dj152auh', # from GET /api/v1/clients
'collection': {
'title': 'Client Program',
'description': 'Optional program description',
'goal': 'hypertrophy',
'image': image_base64,
},
}
response = requests.post(
f'{BASE_URL}/api/v1/collections',
headers=headers,
json=payload,
)
if response.status_code == 200:
print(json.dumps(response.json(), indent=2))
else:
print(f"Error {response.status_code}:", response.text)
Use POST /api/v1/templates to create a workout template and append it to an existing collection. The template is stored in timeline_templates.data and always linked to the top-level collectionId.
Authorization: Bearer YOUR_API_KEY Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
collectionId | integer | Yes | Collection that must already exist for the authenticated user (or resolved client). Template is appended to this collection’s workouts list. |
clientId | string | No | Coach only. Client public_id (e.g. "dj152auh"). If omitted, template is created for the API key owner. |
workout | object | Yes | Template payload (see below). |
Processed by handleTemplateData(). Stored as JSON in timeline_templates.data.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
title | string | No | "My Routine #N" | Template name. Auto-generated if empty, "undefined", or "null". |
description | string | No | "" | Template description. |
note | string | No | "" | Template note. |
color | string | No | random palette color | Hex color (e.g. "#178B76"). If omitted, server picks from internal palette. |
picture | string | No | "" | Cover image. Base64 data URI (data:image/jpeg;base64,...) uploads to S3, or existing https:// URL. Non-CDN URLs may be cleared by sanitization. |
exercises | array | No | [] | List of exercises (order preserved). |
Ignored / server-set on create:
id — assigned by server after insertuser_id — set from auth (or resolved clientId)create_date / update_date — default to current time if omittedWhen adding exercises to a template, copy values from the Lyfta exercise catalog (GET /api/v1/exercises) into the template fields below. Four fields must match exactly:
| Field | Catalog field | Type | Required | Default | Description |
|---|---|---|---|---|---|
exercise_id | id | integer | Yes | 0 | Must match the catalog id from GET /api/v1/exercises. |
excercise_name | name | string | Yes | "" | Must match the catalog name. Field name uses excercise (legacy spelling). |
exercise_type | exercise_type | string | Yes | "" | Must match the catalog exercise_type (e.g. weight_reps, duration). |
exercise_image | image_name | string | Yes | "" | Must match the catalog image_name URL. |
exercise_note | — | string | No | "" | Per-exercise note. |
exercise_rest_time | — | integer | No | 0 | Rest time (seconds). |
exercise_superset_id | — | integer | No | 0 | Groups supersets; 0 = not in a superset. |
is_rep_range_active | — | boolean | No | false | Whether rep range mode is enabled. |
workout_id | — | integer | No | 0 | Legacy; not needed on create. |
date_created | — | string | No | now | ISO/datetime string. |
date_updated | — | string | No | now | ISO/datetime string. |
sets | — | array | No | [] | List of sets for this exercise. |
All set fields are stored as strings (except set_type_id).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
set_type_id | integer | No | 0 | Set type (e.g. normal vs warmup). 0 = default/normal. |
reps | string | No | "" | Reps (e.g. "10"). |
from_reps | string | No | "" | Rep range lower bound (when range mode is used). |
to_reps | string | No | "" | Rep range upper bound. |
weight | string | No | "" | Weight value as string (e.g. "60"). |
rir | string | No | "" | Reps in reserve. |
duration | string | No | "" | Duration (for time-based exercises). |
distance | string | No | "" | Distance (for distance-based exercises). |
Minimal (strength template, one exercise, three sets)
{
"collectionId": 42,
"workout": {
"title": "Push day",
"description": "Chest and shoulders",
"exercises": [
{
"exercise_id": 123,
"excercise_name": "Bench Press",
"exercise_type": "weight_reps",
"exercise_rest_time": 90,
"sets": [
{ "set_type_id": 0, "reps": "10", "weight": "60" },
{ "set_type_id": 0, "reps": "8", "weight": "65" },
{ "set_type_id": 0, "reps": "6", "weight": "70" }
]
}
]
}
}With rep range
{
"collectionId": 42,
"workout": {
"title": "Hypertrophy pull",
"exercises": [
{
"exercise_id": 456,
"excercise_name": "Lat Pulldown",
"exercise_type": "weight_reps",
"is_rep_range_active": true,
"sets": [
{ "from_reps": "10", "to_reps": "12", "weight": "45" },
{ "from_reps": "10", "to_reps": "12", "weight": "45" },
{ "from_reps": "10", "to_reps": "12", "weight": "45" }
]
}
]
}
}Duration-based exercise
{
"collectionId": 42,
"workout": {
"title": "Cardio finisher",
"exercises": [
{
"exercise_id": 789,
"excercise_name": "Treadmill Run",
"exercise_type": "duration",
"sets": [
{ "duration": "600" }
]
}
]
}
}Coach creating for a client
{
"clientId": "dj152auh",
"collectionId": 42,
"workout": {
"title": "Assigned leg day",
"exercises": []
}
}With cover image (base64)
{
"collectionId": 42,
"workout": {
"title": "Push day",
"picture": "data:image/jpeg;base64,/9j/4AAQ...",
"exercises": []
}
}On success, data mirrors what is stored in timeline_templates.data (same shape as GetSingleTemplate / GetTemplates).
{
"status": true,
"message": "Template created successfully",
"collectionId": 42,
"data": {
"id": 9812,
"title": "Push day",
"description": "Chest and shoulders",
"note": "",
"color": "#178B76",
"picture": "https://cdnlyfta.com/images/original/example.jpeg",
"user_id": 1001,
"create_date": "2026-06-15 10:30:00",
"update_date": "2026-06-15 10:30:00",
"exercises": []
}
}Example error:
{
"status": false,
"message": "collectionId is required"
}| Condition | Response |
|---|---|
| Missing/invalid API key | Invalid API key |
| Missing workout | workout is required |
| Missing collectionId | collectionId is required |
| Collection not found for user | Collection not found |
| Invalid clientId (coach) | Not allowed to view this user |
| Rate limit exceeded | HTTP 429 |
Notes for integrators:
exercise_id, excercise_name, exercise_type, and exercise_image must match the catalog id, name, exercise_type, and image_name from GET /api/v1/exercises.excercise_name spelling is intentional in the stored JSON (legacy app field).weight / reps are strings in storage — send "60" not 60 if you want consistency with the app.exercises can be an empty array (template with no exercises yet).collectionId; there is no Favorites fallback on this API.picture: prefer base64 upload or an existing Lyfta CDN URL (apilyfta / cdnlyfta hosts).import requests
import json
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
}
payload = {
'collectionId': 42,
'workout': {
'title': 'Push day',
'description': 'Chest and shoulders',
'exercises': [
{
'exercise_id': 123,
'excercise_name': 'Bench Press',
'exercise_type': 'weight_reps',
'exercise_rest_time': 90,
'sets': [
{'set_type_id': 0, 'reps': '10', 'weight': '60'},
{'set_type_id': 0, 'reps': '8', 'weight': '65'},
{'set_type_id': 0, 'reps': '6', 'weight': '70'},
],
},
],
},
}
response = requests.post(
f'{BASE_URL}/api/v1/templates',
headers=headers,
json=payload,
)
if response.status_code == 200:
print(json.dumps(response.json(), indent=2))
else:
print(f"Error {response.status_code}:", response.text)
import requests
import json
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
}
payload = {
'clientId': 'dj152auh', # client_id from GET /api/v1/clients
'collectionId': 42,
'workout': {
'title': 'Assigned leg day',
'exercises': [],
},
}
response = requests.post(
f'{BASE_URL}/api/v1/templates',
headers=headers,
json=payload,
)
if response.status_code == 200:
print(json.dumps(response.json(), indent=2))
else:
print(f"Error {response.status_code}:", response.text)
List endpoints support pagination via the optional limit and page query parameters. You can request up to 100 workouts per call. Requests specifying a higher limit will be capped at 100.
import requests
import json
API_KEY = 'YOUR_API_KEY' # Replace with your actual API key
BASE_URL = 'https://my.lyfta.app' #
headers = {
'Authorization': f'Bearer {API_KEY}',
}
# Example: Fetch first page of workouts with up to 10 per page
params = {
'limit': 10,
'page': 1,
}
response = requests.get(f'{BASE_URL}/api/v1/workouts', headers=headers, params=params)
if response.status_code == 200:
data = response.json()
print("Workouts:")
print(json.dumps(data, indent=2)) # Pretty print
else:
print(f"Error {response.status_code}:", response.text)
The API returns a list of detailed workouts in the following structure. This is the typical response for GET /api/v1/workouts:
{
"status": boolean,
"count": int,
"total_records": int,
"total_pages": int,
"current_page": int,
"limit": int,
"workouts": [
{
"id": int,
"title": string,
"body_weight": int,
"workout_perform_date": string, // ISO date or datetime
"total_volume": int,
"totalLiftedWeight": int,
"user": {
"username": string
},
"exercises": [
{
"exercise_id": int,
"excercise_name": string,
"exercise_type": string, // e.g. "weight_reps"
"exercise_image": string, // URL
"exercise_rest_time": int,
"sets": [
{
"id": string,
"weight": string, // or float as string
"reps": string, // or int as string
"rir": string,
"duration": string,
"distance": string,
"set_type_id": string,
"is_completed": boolean,
"record_type": string,
"record_level": string,
"record_value": string
}
// ... more sets ...
]
}
// ... more exercises ...
]
}
// ... more workouts ...
]
}Notes:
workouts array.workout contains summary fields, a user object, and an exercises array.exercise contains its own sets array, with minimal set fields.The GET /api/v1/workouts/summary endpoint returns a list of workout summaries with up to 1000 records per call. The structure is as follows:
{
"status": boolean,
"count": int,
"total_records": int,
"total_pages": int,
"current_page": int,
"limit": int,
"workouts": [
{
"id": string, // or int
"title": string,
"description": string | null,
"workout_duration": string, // e.g. "01:06:25"
"total_volume": string, // or int as string
"workout_perform_date": string // ISO date or datetime, e.g. "2025-07-15 06:42:09"
}
// ... more workouts ...
]
}Notes:
status, count, total_records, total_pages, current_page, and limit.limit to control the number of records returned (up to 1000 per call).The GET /api/v1/exercises endpoint returns a list of performed exercises in the following structure:
{
"status": true,
"count": 10,
"current_page": 1,
"limit": 10,
"exercises": [
{
"id": "31",
"name": "Rear Lunge",
"image_name": "https://apilyfta.com/static/GymvisualPNG/00781101-Barbell-Rear-Lunge_Thighs_small.png",
"equipment_id": "[\"1\"]",
"body_part_id": "[\"19\",\"1\"]",
"Target_muscles_id": "[\"13\",\"27\"]",
"Synergist_muscles_id": "[\"3\",\"32\"]",
"exercise_type": "weight_reps"
},
// ... more exercises ...
]
}Notes:
exercises array contains objects for each exercise performed.equipment_id, body_part_id, Target_muscles_id, and Synergist_muscles_id are JSON-encoded arrays of IDs.Use GET /api/v1/exercises/library to search the Lyfta exercise catalog by name. Send the same Authorization: Bearer YOUR_API_KEY header as the other public API endpoints.
Query Parameters:
search (optional): Search text, for example bench press.limit (optional): Number of catalog exercises to return.offset (optional): Zero-based offset for pagination.The response contains catalog exercise records in data.results and cursor-style pagination metadata in data.pagination.
{
"status": true,
"data": {
"results": [
{
"id": "2657",
"name": "Band Bench Press",
"image_name": "https://apilyfta.com/static/GymvisualPNG/12541101-Band-Bench-Press_Chest_small.png",
"equipment_id": "[\"11\"]",
"body_part_id": "[\"2\"]",
"Target_muscles_id": "[\"24\",\"25\"]",
"Synergist_muscles_id": "[\"8\",\"44\"]",
"exercise_type": null
}
// ... more catalog exercises ...
],
"pagination": {
"limit": 10,
"offset": 0,
"total": 108,
"hasMore": true
}
}
}import requests
import json
API_KEY = 'YOUR_API_KEY' # Replace with your actual API key
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
}
# Example: Search the Lyfta exercise catalog
params = {
'search': 'bench press',
'limit': 10,
'offset': 0,
}
response = requests.get(
f'{BASE_URL}/api/v1/exercises/library',
headers=headers,
params=params,
)
if response.status_code == 200:
data = response.json()
print("Exercise library results:")
print(json.dumps(data, indent=2)) # Pretty print
else:
print(f"Error {response.status_code}:", response.text)
Exercise metadata fields such as equipment_id, body_part_id, Target_muscles_id, and Synergist_muscles_id are returned as JSON-encoded arrays of IDs. Parse the string first, then map each ID using the tables below.
| Field | Raw value | Decoded value |
|---|---|---|
equipment_id | ["1"] | Barbell |
body_part_id | ["19","1"] | Quadriceps, Thighs |
Target_muscles_id | ["13","27"] | Gluteus Maximus, Quadriceps |
Synergist_muscles_id | ["3","32"] | Adductor Magnus, Soleus |
Used by equipment_id.
| ID | Name |
|---|---|
1 | Barbell |
2 | Body weight |
3 | Cable |
4 | Dumbbell |
5 | EZ Barbell |
6 | Leverage machine |
7 | Sled machine |
8 | Smith machine |
9 | Weighted |
10 | Assisted |
11 | Band |
12 | Battling Rope |
13 | Bosu ball |
14 | Hammer |
15 | Kettlebell |
16 | Medicine Ball |
17 | Olympic barbell |
18 | Power Sled |
19 | Resistance Band |
20 | Roll |
21 | Rollball |
22 | Rope |
23 | Stability ball |
24 | Stick |
25 | Suspension |
26 | Trap bar |
27 | Vibrate Plate |
28 | Wheel roller |
Used by body_part_id.
| ID | Name |
|---|---|
1 | Thighs |
2 | Chest |
3 | Hips |
4 | Back |
5 | Upper Arms |
6 | Shoulders |
7 | Forearms |
8 | Calves |
9 | Neck |
10 | Cardio |
11 | Full body |
12 | Waist |
13 | Plyometrics |
14 | Weightlifting |
15 | Yoga |
16 | Stretching |
17 | Biceps |
18 | Triceps |
19 | Quadriceps |
20 | Hamstrings |
Used by Target_muscles_id and Synergist_muscles_id.
| ID | Name |
|---|---|
2 | Adductor Longus |
3 | Adductor Magnus |
4 | Biceps Brachii |
5 | Brachialis |
6 | Brachioradialis |
7 | Deep Hip External Rotators |
8 | Deltoid Anterior |
9 | Deltoid Lateral |
10 | Deltoid Posterior |
11 | Erector Spinae |
12 | Gastrocnemius |
13 | Gluteus Maximus |
14 | Gluteus Medius |
15 | Gluteus Minimus |
16 | Gracilis |
17 | Hamstrings |
18 | Iliopsoas |
19 | Infraspinatus |
20 | Latissimus Dorsi |
21 | Levator Scapulae |
22 | Obliques |
23 | Pectineous |
24 | Pectoralis Major Clavicular Head |
25 | Pectoralis Major Sternal Head |
26 | Popliteus |
27 | Quadriceps |
28 | Rectus Abdominis |
29 | Sartorius |
30 | Serratus Ante |
31 | Serratus Anterior |
32 | Soleus |
33 | Splenius |
34 | Sternocleidomastoid |
35 | Subscapularis |
36 | Tensor Fasciae Latae |
37 | Teres Major |
38 | Teres Minor |
39 | Tibialis Anterior |
40 | Transverse Abdominis |
41 | Trapezius Lower Fibers |
42 | Trapezius Middle Fibers |
43 | Trapezius Upper Fibers |
44 | Triceps Brachii |
45 | Wrist Extensors |
46 | Wrist Flexors |
Coaches on a paid Coach or Scale plan can use the same API key generated above to access client workout and exercise data programmatically. Authenticate with the same Authorization: Bearer YOUR_API_KEY header on every request.
Call GET /api/v1/clients to retrieve your coaching clients. Each client object includes a client_id you will pass to the other endpoints. Active clients have status set to "1"; pending invitations use "0".
{
"status": true,
"data": [
{
"username": "Hemant (test user)1",
"photo": "https://cdnlyfta.com/images/original/profilePic_69933e5d8a0836.499124082785.jpeg",
"first": "Hemant",
"last": "(test user)1",
"status": "1",
"client_id": "dj152auh"
}
// ... more clients ...
]
}Add the client_id query parameter to any of the existing workout and exercise endpoints to return data for that client instead of your own account:
GET /api/v1/workoutsGET /api/v1/workouts/summaryGET /api/v1/exercisesGET /api/v1/exercises/progress (also requires exercise_id and duration)POST /api/v1/collections — include client_id in the JSON body (see Create Collection)POST /api/v1/templates — include clientId in the JSON body (see Create Template)Example params object:
params = {
'client_id': 'dj152auh', // from GET /api/v1/clients
'limit': 10,
'page': 1,
}Notes:
client_id to fetch your own data, as before.import requests
import json
API_KEY = 'YOUR_API_KEY' # Same key as the personal data endpoints
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
}
# Step 1: Fetch active clients and collect their client_id values
clients_response = requests.get(f'{BASE_URL}/api/v1/clients', headers=headers)
if clients_response.status_code != 200:
print(f"Error {clients_response.status_code}:", clients_response.text)
exit()
clients_data = clients_response.json()
active_clients = [
client for client in clients_data.get('data', [])
if str(client.get('status')) == '1'
]
for client in active_clients:
client_id = client['client_id']
print(f"Active client: {client.get('username')} (client_id={client_id})")
# Step 2: Pass client_id to other endpoints to fetch that client's data
params = {
'client_id': client_id,
'limit': 10,
'page': 1,
}
workouts_response = requests.get(
f'{BASE_URL}/api/v1/workouts',
headers=headers,
params=params,
)
if workouts_response.status_code == 200:
print(json.dumps(workouts_response.json(), indent=2))
else:
print(f"Error {workouts_response.status_code}:", workouts_response.text)
The GET /api/v1/exercises/progress endpoint returns progress data for a specific exercise in the following structure:
{
"status": true,
"weight_unit": "kg",
"data": [
{
"date": "2025-07-21",
"best_weight": 110,
"best_reps": 15,
"best_volume": 800,
"estimated_rm": "128"
},
{
"date": "2025-07-14",
"best_weight": 120,
"best_reps": 15,
"best_volume": 1000,
"estimated_rm": "133"
},
{
"date": "2025-06-30",
"best_weight": 80,
"best_reps": 20,
"best_volume": 1600,
"estimated_rm": "133"
}
// ... more records ...
]
}Query Parameters:
duration (required): Number of days to include in the progress data (e.g., 365 for one year).exercise_id (required): The ID of the exercise to fetch progress for (from the performed exercise list IDs).Example params object for a request:
params = {
'duration': 365, // in days
'exercise_id': 2, // from the performed exercise list ids
}Notes:
data array contains progress records for the exercise, typically grouped by date.best_weight, best_reps, and best_volume represent the best set or total for that day.estimated_rm is the estimated one-rep max, usually as a string.weight_unit indicates the unit for weight values (e.g., kg or lb).