/api/v1/workoutsDetailed workouts with exercises and sets. Optional UTC date filters.
Open endpointGenerate credentials, authenticate requests, and understand the workout and exercise data returned by the Lyfta API.
https://my.lyfta.appLyfta MCP is a read-only training connector. Paste https://api.lyftadev.com/v2/mcp only — not my.lyfta.app, not /community/mcp. After they paste it, the client should DCR and show Connect. Do not paste a personal JWT.
Your 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.
Send your API key on every request. The base URL is https://my.lyfta.app.
Authorization: Bearer YOUR_API_KEY
You can make up to 60 requests per minute and 5,000 requests per day. Exceeding these limits returns 429 Too Many Requests.
Jump to a reference section. All paths are relative to https://my.lyfta.app.
/api/v1/workoutsDetailed workouts with exercises and sets. Optional UTC date filters.
Open endpoint/api/v1/workouts/summaryHigh-level workout summaries without exercise or set details.
Open endpoint/api/v1/exercisesExercises you have performed, including catalog metadata.
Open endpoint/api/v1/exercises/librarySearch the Lyfta exercise catalog by name.
Open endpoint/api/v1/exercises/progressBest-set progress for one exercise over a date range.
Open endpoint/api/v1/collectionsSaved programs/collections. List items include id, title, image, workout_ids, and workout_count.
Open endpoint/api/v1/collections/:idOne collection. view=summary returns workout cards; view=full includes template JSON.
Open endpoint/api/v1/templatesSaved workout templates as cards (title, picture, exercise_count, collection).
Open endpoint/api/v1/templates/:idFull saved workout template, including exercises and sets.
Open endpoint/api/v1/scheduleUpcoming calendar and followed-plan workouts. Defaults to today through +90 days, incomplete only.
Open endpoint/api/v1/collectionsCreate a program/collection in your library or a client library.
Open endpoint/api/v1/templatesCreate a workout template and append it to an existing collection.
Open endpoint/api/v1/clientsList coaching clients. Use client_id on other endpoints.
Open endpoint/api/v1/workoutsReturns detailed workouts with exercises and sets. Omit from and to to fetch all workouts, as before.
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Max workouts to return. Capped at 100. |
page | integer | No | Page number for list results. |
from | string | No | Inclusive UTC start date, YYYY-MM-DD. |
to | string | No | Inclusive UTC end date, YYYY-MM-DD. |
client_id | string | No | Coach only. Public client_id from GET /api/v1/clients (lyfta.coach.public_id). Numeric IDs still work. Key owner must be the accepted coach. |
Dates are UTC calendar days and filter on indexed timeline_time, not the JSON snapshot. Invalid dates, or a from after to, return 400.
GET /api/v1/workouts?limit=1GET /api/v1/workouts?from=2026-09-02&to=2026-09-02GET /api/v1/workouts?from=2026-09-01&to=2026-09-08GET /api/v1/workouts?client_id=dj152auh{
"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.user object, and an exercises array.sets array.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,
}
# Optional UTC calendar-day filters (inclusive). Omit from/to for all workouts.
# params['from'] = '2026-09-01'
# params['to'] = '2026-09-08'
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)
/api/v1/workouts/summaryReturns workout headers only — no exercises or sets. Soft-deleted workouts are omitted and results are ordered by id descending. Up to 1000 records per call.
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Max summaries to return. Default 20, capped at 1000. |
page | integer | No | Page number for list results. |
client_id | string | No | Coach only. Public client_id from GET /api/v1/clients (lyfta.coach.public_id). Numeric IDs still work. |
GET /api/v1/workouts/summary?limit=2&page=1GET /api/v1/workouts/summary?client_id=dj152auh{
"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.current_page is the resolved page number.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}',
}
params = {
'limit': 2,
'page': 1,
}
response = requests.get(f'{BASE_URL}/api/v1/workouts/summary', headers=headers, params=params)
print(json.dumps(response.json(), indent=2))
/api/v1/exercisesReturns exercises you have performed, including catalog metadata used when creating templates.
{
"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
equipment_id, body_part_id, Target_muscles_id, and Synergist_muscles_id are JSON-encoded arrays of IDs. See ID mappings./api/v1/exercises/librarySearch the Lyfta exercise catalog by name. Results are in data.results with cursor-style pagination in data.pagination.
| Field | Type | Required | Description |
|---|---|---|---|
search | string | No | Search text, for example bench press. |
limit | integer | No | Number of catalog exercises to return. |
offset | integer | No | Zero-based offset for 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)
/api/v1/exercises/progressReturns best-set progress for one exercise over a number of days. Both duration and exercise_id are required.
| Field | Type | Required | Description |
|---|---|---|---|
duration | integer | Yes | Number of days to include, for example 365. |
exercise_id | integer | Yes | Exercise ID from the performed exercise list. |
client_id | string | No | Coach only. Return this client’s progress instead of your own. |
params = {
'duration': 365, // in days
'exercise_id': 2, // from the performed exercise list ids
}{
"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 ...
]
}Notes
data is typically grouped by date.best_weight, best_reps, and best_volume are the best set or total for that day.estimated_rm is the estimated one-rep max, usually as a string.weight_unit is kg or lb./api/v1/collectionsReturns saved programs/collections for the API key owner. A personal API key is enough; this used to be JWT-only. List items include id, title, image, workout_ids, and workout_count.
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Max collections to return. |
page | integer | No | Page number for list results. |
client_id | string | No | Coach only. Public client_id from GET /api/v1/clients. |
{
"status": true,
"count": 2,
"total_records": 14,
"total_pages": 7,
"current_page": 1,
"limit": 2,
"collections": [
{
"id": 52,
"title": "Favorites",
"image": "",
"pinned": "1",
"workout_ids": [15447045, 11683978],
"workout_count": 7,
"date_created": "2023-02-22T22:25:22.000Z",
"date_updated": "2026-09-03T13:48:30.916Z"
}
]
}import requests
import json
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
}
response = requests.get(
f'{BASE_URL}/api/v1/collections',
headers=headers,
params={'limit': 20, 'page': 1},
)
print(json.dumps(response.json(), indent=2))
/api/v1/collections/:idReturns one collection. view=summary (default) includes workout cards.view=full includes the complete saved template JSON for each workout.
| Field | Type | Required | Description |
|---|---|---|---|
view | string | No | summary (default) returns workout cards. full includes complete template JSON with exercises and sets. |
client_id | string | No | Coach only. Public client_id from GET /api/v1/clients. |
{
"status": true,
"data": {
"id": 52,
"title": "Favorites",
"description": "",
"image": "",
"pinned": "1",
"workout_ids": [15447045, 11683978],
"workout_count": 7,
"workouts": [
{
"id": 15447045,
"title": "Lyfta icon test",
"picture": "https://cdnlyfta.com/images/original/example.null",
"color": "#EB7E83",
"duration": null,
"exercise_count": 4
}
]
}
}import requests
import json
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
}
response = requests.get(
f'{BASE_URL}/api/v1/collections/52',
headers=headers,
params={'view': 'summary'},
)
print(json.dumps(response.json(), indent=2))
/api/v1/templatesReturns saved workout templates as cards: title, picture, exercise count, and the parent collection.
| Field | Type | Required | Description |
|---|---|---|---|
limit | integer | No | Max templates to return. |
page | integer | No | Page number for list results. |
client_id | string | No | Coach only. Public client_id from GET /api/v1/clients. |
{
"status": true,
"count": 1,
"total_records": 69,
"total_pages": 69,
"current_page": 1,
"limit": 1,
"templates": [
{
"id": 1158669,
"title": "Full Body day",
"picture": "https://apilyfta.com/uploads/workouts/example.jpg",
"color": "#EB445A",
"exercise_count": 7,
"date_created": "2023-09-25T19:44:15.000Z",
"date_updated": "2024-07-12T09:58:54.000Z",
"collection_id": 103385,
"collection_title": "Zyzz 5-day split"
}
]
}import requests
import json
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
}
response = requests.get(
f'{BASE_URL}/api/v1/templates',
headers=headers,
params={'limit': 20, 'page': 1},
)
print(json.dumps(response.json(), indent=2))
/api/v1/templates/:idReturns the full saved workout, including exercises and sets. Use this after picking an id from the template list.
| Field | Type | Required | Description |
|---|---|---|---|
client_id | string | No | Coach only. Public client_id from GET /api/v1/clients. |
{
"status": true,
"data": {
"id": 1158669,
"title": "Full Body day",
"note": "",
"color": "#EB445A",
"picture": "https://apilyfta.com/uploads/workouts/example.jpg",
"user_id": "35",
"exercises": [
{
"exercise_id": 7,
"excercise_name": "Deadlift",
"exercise_type": "",
"exercise_image": "https://apilyfta.com/static/GymvisualPNG/example.png",
"sets": [
{ "set_type_id": 0, "reps": "12", "weight": "" }
]
}
]
}
}Notes
excercise_name spelling is intentional (legacy app field).weight and reps are strings.import requests
import json
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
}
response = requests.get(
f'{BASE_URL}/api/v1/templates/1158669',
headers=headers,
)
print(json.dumps(response.json(), indent=2))
/api/v1/scheduleCombines calendar workouts and followed-plan workouts. Defaults: from = today, to = +90 days, incomplete only. Completed history is still GET /api/v1/workouts and GET /api/v1/workouts/summary.
| Field | Type | Required | Description |
|---|---|---|---|
from | string | No | Inclusive UTC start date, YYYY-MM-DD. Defaults to today. |
to | string | No | Inclusive UTC end date, YYYY-MM-DD. Defaults to today + 90 days. |
include_completed | boolean | No | Set true to include completed scheduled workouts. Default is incomplete only. |
client_id | string | No | Coach only. Public client_id from GET /api/v1/clients. |
GET /api/v1/scheduleGET /api/v1/schedule?from=2026-09-08&to=2026-09-14GET /api/v1/schedule?include_completed=true{
"status": true,
"count": 1,
"total_records": 1,
"total_pages": 1,
"current_page": 1,
"limit": 50,
"from": "2026-08-01",
"to": "2026-09-14",
"workouts": [
{
"id": "251981",
"source": "calendar",
"template_id": "15303093",
"title": "Upper Body 1",
"scheduled_date": "2026-08-20",
"scheduled_time": "08:00:00",
"status": "scheduled",
"is_completed": false,
"note": null
}
]
}import requests
import json
API_KEY = 'YOUR_API_KEY'
BASE_URL = 'https://my.lyfta.app'
headers = {
'Authorization': f'Bearer {API_KEY}',
}
# Defaults: from = today, to = +90 days, incomplete only
upcoming = requests.get(f'{BASE_URL}/api/v1/schedule', headers=headers)
print(json.dumps(upcoming.json(), indent=2))
# Include completed items in a date range
completed = requests.get(
f'{BASE_URL}/api/v1/schedule',
headers=headers,
params={
'from': '2026-09-08',
'to': '2026-09-14',
'include_completed': 'true',
},
)
print(json.dumps(completed.json(), indent=2))
/api/v1/collectionsCreate a program/collection. Send Authorization: Bearer YOUR_API_KEY and Content-Type: application/json. The body must include a collection object. Coaches can add client_id to create it in a client's library.
| Field | Type | Required | Description |
|---|---|---|---|
collection.title | string | Yes | Non-empty title after trimming. Returns "Title is required" if missing or blank. |
collection.description | string | No | Program description. |
collection.goal | string | No | Program goal, for example strength or hypertrophy. |
collection.image | string | No | Cover image as a base64-encoded string. URLs are not accepted. |
client_id | string | No | Coach only. Client ID from GET /api/v1/clients. Omit to create in your own library. |
{
"collection": {
"title": "My Program",
"description": "Optional program description",
"goal": "Optional goal, e.g. strength or hypertrophy",
"image": "/9j/4AAQSkZJRg...base64-encoded image data..."
}
}Coach requests require a paid Coach or Scale plan and a linked client with status "1".
On success, data includes 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"
}
}{
"status": false,
"message": "Title is required"
}Image handling
collection.image must be a base64-encoded image string. URLs are not accepted.data.image is the 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)
/api/v1/templatesCreate a workout template and append it to an existing collection. The template is stored in timeline_templates.data and always linked to 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() and 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 / set by the server
id — assigned after insertuser_id — set from auth or resolved clientIdcreate_date / update_date — default to now if omittedCopy values from the catalog (GET /api/v1/exercises). 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). |
{
"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" }
]
}
]
}
}{
"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" }
]
}
]
}
}{
"collectionId": 42,
"workout": {
"title": "Cardio finisher",
"exercises": [
{
"exercise_id": 789,
"excercise_name": "Treadmill Run",
"exercise_type": "duration",
"sets": [
{ "duration": "600" }
]
}
]
}
}{
"collectionId": 42,
"workout": {
"title": "Push day",
"picture": "data:image/jpeg;base64,/9j/4AAQ...",
"exercises": []
}
}On success, data mirrors 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": []
}
}{
"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 catalog id, name, exercise_type, and image_name.excercise_name spelling is intentional (legacy app field).weight and reps as strings, e.g. "60".exercises can be an empty array.collectionId; there is no Favorites fallback.picture or an existing Lyfta CDN URL (apilyfta / cdnlyfta).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)
/api/v1/clientsCoaches on a paid Coach or Scale plan can use the same API key to read and write client training data. Authenticate with Authorization: Bearer YOUR_API_KEY.
GET /api/v1/clients returns the key owner's coaching clients. Each client includes a client_id you can pass to other endpoints, including GET /api/v1/workouts?client_id=.... Active (accepted) clients have status "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 ...
]
}Notes
client_id is lyfta.coach.public_id, not the numeric user id.client_id values still work on workouts for existing callers.Add client_id to read endpoints, or include it in write bodies:
GET /api/v1/workoutsGET /api/v1/workouts/summaryGET /api/v1/exercisesGET /api/v1/exercises/progress (also requires exercise_id and duration)GET /api/v1/collections and GET /api/v1/collections/:idGET /api/v1/templates and GET /api/v1/templates/:idGET /api/v1/schedulePOST /api/v1/collections — client_id in the JSON body (Create collection)POST /api/v1/templates — clientId in the JSON body (Create template)params = {
'client_id': 'dj152auh', // from GET /api/v1/clients
'limit': 10,
'page': 1,
}Notes
client_id to fetch your own data.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)
List endpoints accept optional limit and page query parameters. Workouts are capped at 100 per call. Summaries allow up to 1000.limit, page, and client_id work together with date filters on GET /api/v1/workouts.
equipment_id, body_part_id, Target_muscles_id, and Synergist_muscles_id are JSON-encoded arrays of IDs. Parse the string first, then map each ID with 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 |