Lyfta Developer API

API Access

Generate credentials, authenticate requests, and understand the workout and exercise data returned by the Lyfta API.

Base URLhttps://my.lyfta.app
60requests per minute
5,000requests per day
100max workouts per page
Agents

MCP for ChatGPT and Claude

Lyfta 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.

Open MCP docs
Credentials

Generate an API key

Shown once

Your API key provides programmatic access to your Lyfta data. Use the button below to generate an API key.

Important Notes:

  • Generating a new API key will automatically revoke and replace any previously active key.
  • Your API key is displayed only once upon generation. Please copy and store it in a safe place immediately. You will not be able to retrieve it again.
1

Generate your key

Create an API key above. A new key revokes and replaces any previously active key.

2

Add the bearer header

Send your key in the authorization header on every request.

3

Page through data

Use limit and page to control list responses.

Authentication

Send your API key on every request. The base URL is https://my.lyfta.app.

Authorization header
Authorization: Bearer YOUR_API_KEY

Rate limits

429 on exceed

You can make up to 60 requests per minute and 5,000 requests per day. Exceeding these limits returns 429 Too Many Requests.

Endpoint index

Jump to a reference section. All paths are relative to https://my.lyfta.app.

Read

GET/api/v1/workouts

Detailed workouts with exercises and sets. Optional UTC date filters.

Open endpoint
GET/api/v1/workouts/summary

High-level workout summaries without exercise or set details.

Open endpoint
GET/api/v1/exercises

Exercises you have performed, including catalog metadata.

Open endpoint
GET/api/v1/exercises/library

Search the Lyfta exercise catalog by name.

Open endpoint
GET/api/v1/exercises/progress

Best-set progress for one exercise over a date range.

Open endpoint
GET/api/v1/collections

Saved programs/collections. List items include id, title, image, workout_ids, and workout_count.

Open endpoint
GET/api/v1/collections/:id

One collection. view=summary returns workout cards; view=full includes template JSON.

Open endpoint
GET/api/v1/templates

Saved workout templates as cards (title, picture, exercise_count, collection).

Open endpoint
GET/api/v1/templates/:id

Full saved workout template, including exercises and sets.

Open endpoint
GET/api/v1/schedule

Upcoming calendar and followed-plan workouts. Defaults to today through +90 days, incomplete only.

Open endpoint

Write

POST/api/v1/collections

Create a program/collection in your library or a client library.

Open endpoint
POST/api/v1/templates

Create a workout template and append it to an existing collection.

Open endpoint

Coach

GET/api/v1/clients

List coaching clients. Use client_id on other endpoints.

Open endpoint
GET

List workouts

/api/v1/workouts

Returns detailed workouts with exercises and sets. Omit from and to to fetch all workouts, as before.

Query parameters

FieldTypeRequiredDescription
limitintegerNoMax workouts to return. Capped at 100.
pageintegerNoPage number for list results.
fromstringNoInclusive UTC start date, YYYY-MM-DD.
tostringNoInclusive UTC end date, YYYY-MM-DD.
client_idstringNoCoach 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.

Common requests

  • Latest completed workout: GET /api/v1/workouts?limit=1
  • One day: GET /api/v1/workouts?from=2026-09-02&to=2026-09-02
  • Date range: GET /api/v1/workouts?from=2026-09-01&to=2026-09-08
  • Client workouts: GET /api/v1/workouts?client_id=dj152auh

Response

GET /api/v1/workouts response
{
  "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

  • The top-level object contains pagination and status fields, and a workouts array.
  • Each workout contains summary fields, a user object, and an exercises array.
  • Each exercise contains its own sets array.
  • Some fields may be optional or null depending on the workout type.

Python

Python — first page of workouts
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)
GET

List workout summaries

/api/v1/workouts/summary

Returns 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.

Query parameters

FieldTypeRequiredDescription
limitintegerNoMax summaries to return. Default 20, capped at 1000.
pageintegerNoPage number for list results.
client_idstringNoCoach only. Public client_id from GET /api/v1/clients (lyfta.coach.public_id). Numeric IDs still work.
  • First page: GET /api/v1/workouts/summary?limit=2&page=1
  • Client summaries: GET /api/v1/workouts/summary?client_id=dj152auh

Response

GET /api/v1/workouts/summary response
{
  "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

  • The summary object omits exercise and set details.
  • All fields are strings except status, count, total_records, total_pages, current_page, and limit.
  • current_page is the resolved page number.
  • Same API-key auth and rate limits as the other public routes: 60/min and 5,000/day.

Python

Python - first page of workout summaries
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))
GET

List performed exercises

/api/v1/exercises

Returns exercises you have performed, including catalog metadata used when creating templates.

Response

GET /api/v1/exercises response
{
  "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

  • IDs may be strings or numbers depending on the source.
  • equipment_id, body_part_id, Target_muscles_id, and Synergist_muscles_id are JSON-encoded arrays of IDs. See ID mappings.
GET

Search exercise library

/api/v1/exercises/library

Search the Lyfta exercise catalog by name. Results are in data.results with cursor-style pagination in data.pagination.

Query parameters

FieldTypeRequiredDescription
searchstringNoSearch text, for example bench press.
limitintegerNoNumber of catalog exercises to return.
offsetintegerNoZero-based offset for pagination.

Response

GET /api/v1/exercises/library response
{
  "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
    }
  }
}

Python

Python — search exercise library
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)
GET

Get exercise progress

/api/v1/exercises/progress

Returns best-set progress for one exercise over a number of days. Both duration and exercise_id are required.

Query parameters

FieldTypeRequiredDescription
durationintegerYesNumber of days to include, for example 365.
exercise_idintegerYesExercise ID from the performed exercise list.
client_idstringNoCoach only. Return this client’s progress instead of your own.
Example params
params = {
  'duration': 365, // in days
  'exercise_id': 2, // from the performed exercise list ids
}

Response

GET /api/v1/exercises/progress response
{
  "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.
GET

List collections

/api/v1/collections

Returns 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.

Query parameters

FieldTypeRequiredDescription
limitintegerNoMax collections to return.
pageintegerNoPage number for list results.
client_idstringNoCoach only. Public client_id from GET /api/v1/clients.

Response

GET /api/v1/collections response
{
  "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"
    }
  ]
}

Python

Python — list collections
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))
GET

Get collection

/api/v1/collections/:id

Returns one collection. view=summary (default) includes workout cards.view=full includes the complete saved template JSON for each workout.

Query parameters

FieldTypeRequiredDescription
viewstringNosummary (default) returns workout cards. full includes complete template JSON with exercises and sets.
client_idstringNoCoach only. Public client_id from GET /api/v1/clients.

Response

GET /api/v1/collections/:id?view=summary
{
  "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
      }
    ]
  }
}

Python

Python — get collection
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))
GET

List templates

/api/v1/templates

Returns saved workout templates as cards: title, picture, exercise count, and the parent collection.

Query parameters

FieldTypeRequiredDescription
limitintegerNoMax templates to return.
pageintegerNoPage number for list results.
client_idstringNoCoach only. Public client_id from GET /api/v1/clients.

Response

GET /api/v1/templates response
{
  "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"
    }
  ]
}

Python

Python — list templates
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))
GET

Get template

/api/v1/templates/:id

Returns the full saved workout, including exercises and sets. Use this after picking an id from the template list.

Query parameters

FieldTypeRequiredDescription
client_idstringNoCoach only. Public client_id from GET /api/v1/clients.

Response

GET /api/v1/templates/:id response
{
  "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.

Python

Python — get template
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))
GET

List upcoming schedule

/api/v1/schedule

Combines 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.

Query parameters

FieldTypeRequiredDescription
fromstringNoInclusive UTC start date, YYYY-MM-DD. Defaults to today.
tostringNoInclusive UTC end date, YYYY-MM-DD. Defaults to today + 90 days.
include_completedbooleanNoSet true to include completed scheduled workouts. Default is incomplete only.
client_idstringNoCoach only. Public client_id from GET /api/v1/clients.
  • Next 90 days, incomplete only: GET /api/v1/schedule
  • Date range: GET /api/v1/schedule?from=2026-09-08&to=2026-09-14
  • Include completed: GET /api/v1/schedule?include_completed=true

Response

GET /api/v1/schedule response
{
  "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
    }
  ]
}

Python

Python — upcoming schedule
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))
POST

Create collection

/api/v1/collections

Create 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.

Request fields

FieldTypeRequiredDescription
collection.titlestringYesNon-empty title after trimming. Returns "Title is required" if missing or blank.
collection.descriptionstringNoProgram description.
collection.goalstringNoProgram goal, for example strength or hypertrophy.
collection.imagestringNoCover image as a base64-encoded string. URLs are not accepted.
client_idstringNoCoach only. Client ID from GET /api/v1/clients. Omit to create in your own library.

Request

POST /api/v1/collections
{
  "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".

Response

On success, data includes the new id.

Success response
{
  "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 response
{
  "status": false,
  "message": "Title is required"
}

Image handling

  • collection.image must be a base64-encoded image string. URLs are not accepted.
  • The collection is created first, then the image is uploaded. data.image is the hosted URL.
  • Omit collection.image to create a collection without a cover image.

Python

Python — create collection
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)
POST

Create template

/api/v1/templates

Create a workout template and append it to an existing collection. The template is stored in timeline_templates.data and always linked to collectionId.

Headers

Required headers
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Top-level fields

FieldTypeRequiredDescription
collectionIdintegerYesCollection that must already exist for the authenticated user (or resolved client). Template is appended to this collection’s workouts list.
clientIdstringNoCoach only. Client public_id (e.g. "dj152auh"). If omitted, template is created for the API key owner.
workoutobjectYesTemplate payload (see below).

workout object

Processed by handleTemplateData() and stored as JSON in timeline_templates.data.

FieldTypeRequiredDefaultDescription
titlestringNo"My Routine #N"Template name. Auto-generated if empty, "undefined", or "null".
descriptionstringNo""Template description.
notestringNo""Template note.
colorstringNorandom palette colorHex color (e.g. "#178B76"). If omitted, server picks from internal palette.
picturestringNo""Cover image. Base64 data URI (data:image/jpeg;base64,...) uploads to S3, or existing https:// URL. Non-CDN URLs may be cleared by sanitization.
exercisesarrayNo[]List of exercises (order preserved).

Ignored / set by the server

  • id — assigned after insert
  • user_id — set from auth or resolved clientId
  • create_date / update_date — default to now if omitted

workout.exercises[]

Copy values from the catalog (GET /api/v1/exercises). Four fields must match exactly:

FieldCatalog fieldTypeRequiredDefaultDescription
exercise_ididintegerYes0Must match the catalog id from GET /api/v1/exercises.
excercise_namenamestringYes""Must match the catalog name. Field name uses excercise (legacy spelling).
exercise_typeexercise_typestringYes""Must match the catalog exercise_type (e.g. weight_reps, duration).
exercise_imageimage_namestringYes""Must match the catalog image_name URL.
exercise_notestringNo""Per-exercise note.
exercise_rest_timeintegerNo0Rest time (seconds).
exercise_superset_idintegerNo0Groups supersets; 0 = not in a superset.
is_rep_range_activebooleanNofalseWhether rep range mode is enabled.
workout_idintegerNo0Legacy; not needed on create.
date_createdstringNonowISO/datetime string.
date_updatedstringNonowISO/datetime string.
setsarrayNo[]List of sets for this exercise.

workout.exercises[].sets[]

All set fields are stored as strings except set_type_id.

FieldTypeRequiredDefaultDescription
set_type_idintegerNo0Set type (e.g. normal vs warmup). 0 = default/normal.
repsstringNo""Reps (e.g. "10").
from_repsstringNo""Rep range lower bound (when range mode is used).
to_repsstringNo""Rep range upper bound.
weightstringNo""Weight value as string (e.g. "60").
rirstringNo""Reps in reserve.
durationstringNo""Duration (for time-based exercises).
distancestringNo""Distance (for distance-based exercises).

Example requests

Minimal request
{
  "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" }
        ]
      }
    ]
  }
}
Rep range request
Rep range request
{
  "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
Duration request
{
  "collectionId": 42,
  "workout": {
    "title": "Cardio finisher",
    "exercises": [
      {
        "exercise_id": 789,
        "excercise_name": "Treadmill Run",
        "exercise_type": "duration",
        "sets": [
          { "duration": "600" }
        ]
      }
    ]
  }
}
Cover image (base64)
Cover image request
{
  "collectionId": 42,
  "workout": {
    "title": "Push day",
    "picture": "data:image/jpeg;base64,/9j/4AAQ...",
    "exercises": []
  }
}

Response

On success, data mirrors timeline_templates.data (same shape as GetSingleTemplate / GetTemplates).

Success response
{
  "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": []
  }
}
Error response
{
  "status": false,
  "message": "collectionId is required"
}

Validation & errors

ConditionResponse
Missing/invalid API keyInvalid API key
Missing workoutworkout is required
Missing collectionIdcollectionId is required
Collection not found for userCollection not found
Invalid clientId (coach)Not allowed to view this user
Rate limit exceededHTTP 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).
  • Send weight and reps as strings, e.g. "60".
  • exercises can be an empty array.
  • The template is always linked to collectionId; there is no Favorites fallback.
  • Prefer a base64 picture or an existing Lyfta CDN URL (apilyfta / cdnlyfta).

Python

Python — create template
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)
GET

Coach API

/api/v1/clients
Beta

Coaches 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.

List clients

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".

GET /api/v1/clients response
{
  "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.
  • Numeric client_id values still work on workouts for existing callers.
  • The key owner must be the accepted coach for that client.
  • Soft-deleted coach-client relationships are omitted.

Fetch or write client data

Add client_id to read endpoints, or include it in write bodies:

  • GET /api/v1/workouts
  • GET /api/v1/workouts/summary
  • GET /api/v1/exercises
  • GET /api/v1/exercises/progress (also requires exercise_id and duration)
  • GET /api/v1/collections and GET /api/v1/collections/:id
  • GET /api/v1/templates and GET /api/v1/templates/:id
  • GET /api/v1/schedule
  • POST /api/v1/collectionsclient_id in the JSON body (Create collection)
  • POST /api/v1/templatesclientId in the JSON body (Create template)
Params with client_id
params = {
  'client_id': 'dj152auh', // from GET /api/v1/clients
  'limit': 10,
  'page': 1,
}

Notes

  • Omit client_id to fetch your own data.
  • You can only request data for accepted clients linked to the API key owner.
  • Response shapes match the endpoint sections above.

Python

Python — list clients and fetch workouts
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)

Exercise ID mappings

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.

Example decoded values

FieldRaw valueDecoded 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

Equipment IDs

Used by equipment_id.

IDName
1Barbell
2Body weight
3Cable
4Dumbbell
5EZ Barbell
6Leverage machine
7Sled machine
8Smith machine
9Weighted
10Assisted
11Band
12Battling Rope
13Bosu ball
14Hammer
15Kettlebell
16Medicine Ball
17Olympic barbell
18Power Sled
19Resistance Band
20Roll
21Rollball
22Rope
23Stability ball
24Stick
25Suspension
26Trap bar
27Vibrate Plate
28Wheel roller

Body Part IDs

Used by body_part_id.

IDName
1Thighs
2Chest
3Hips
4Back
5Upper Arms
6Shoulders
7Forearms
8Calves
9Neck
10Cardio
11Full body
12Waist
13Plyometrics
14Weightlifting
15Yoga
16Stretching
17Biceps
18Triceps
19Quadriceps
20Hamstrings

Muscle IDs

Used by Target_muscles_id and Synergist_muscles_id.

IDName
2Adductor Longus
3Adductor Magnus
4Biceps Brachii
5Brachialis
6Brachioradialis
7Deep Hip External Rotators
8Deltoid Anterior
9Deltoid Lateral
10Deltoid Posterior
11Erector Spinae
12Gastrocnemius
13Gluteus Maximus
14Gluteus Medius
15Gluteus Minimus
16Gracilis
17Hamstrings
18Iliopsoas
19Infraspinatus
20Latissimus Dorsi
21Levator Scapulae
22Obliques
23Pectineous
24Pectoralis Major Clavicular Head
25Pectoralis Major Sternal Head
26Popliteus
27Quadriceps
28Rectus Abdominis
29Sartorius
30Serratus Ante
31Serratus Anterior
32Soleus
33Splenius
34Sternocleidomastoid
35Subscapularis
36Tensor Fasciae Latae
37Teres Major
38Teres Minor
39Tibialis Anterior
40Transverse Abdominis
41Trapezius Lower Fibers
42Trapezius Middle Fibers
43Trapezius Upper Fibers
44Triceps Brachii
45Wrist Extensors
46Wrist Flexors