REST API
What is the Liftosaur API?
Liftosaur has a REST API that lets you manage your programs and workout history programmatically. You can create programs, log workouts, simulate progressions, and pull stats - all from scripts, apps, or anything that can make HTTP requests.
Requires a premium subscription.
Getting an API Key
- Open Liftosaur and go to Settings
- Tap API Keys
- Tap Create API Key and give it a name
- Copy the key - it starts with
lftsk_
Keep it secret. If you lose it, delete it and create a new one.
Authentication
Pass your API key as a Bearer token in the Authorization header:
Authorization: Bearer lftsk_your_key_here
Every request needs this header. Without it you'll get a 401 error.
Base URL
https://www.liftosaur.com/api/v1
Endpoints
List Programs
GET /api/v1/programs
Returns all your programs with their IDs, names, and which one is currently active.
Response:
{
"data": {
"programs": [
{ "id": "abc123", "name": "5/3/1 BBB", "isCurrent": true },
{ "id": "def456", "name": "GZCLP", "isCurrent": false }
]
}
}
Get a Program
GET /api/v1/programs/:id
Returns the program's name and full source code in Liftoscript format. Use id=current to get the currently active program.
Response:
{
"data": {
"id": "abc123",
"name": "My Program",
"text": "# Week 1\n## Day 1\nSquat / 3x5 / 135lb / progress: lp(5lb)",
"isCurrent": true
}
}
Create a Program
POST /api/v1/programs
Content-Type: application/json
{
"name": "My Program",
"text": "# Week 1\n## Day 1\nSquat / 3x5 / 135lb / progress: lp(5lb)"
}
The text field must be valid Liftoscript. If there are syntax errors, you'll get a 422 response with line numbers and error messages.
Response (201):
{
"data": {
"id": "abc123",
"name": "My Program",
"text": "# Week 1\n## Day 1\nSquat / 3x5 / 135lb / progress: lp(5lb)"
}
}
Update a Program
PUT /api/v1/programs/:id
Content-Type: application/json
{
"text": "# Week 1\n## Day 1\nSquat / 5x5 / 185lb / progress: lp(10lb)",
"name": "Updated Name"
}
name is optional - if omitted, keeps the existing name. Use id=current to update the active program.
Response:
{
"data": {
"id": "abc123",
"name": "Updated Name",
"text": "# Week 1\n## Day 1\nSquat / 5x5 / 185lb / progress: lp(10lb)",
"isCurrent": true
}
}
Delete a Program
DELETE /api/v1/programs/:id
Can't delete the currently active program.
Response:
{
"data": {
"deleted": true
}
}
List History
GET /api/v1/history?limit=50&startDate=2026-01-01&endDate=2026-03-01
Query parameters (all optional):
startDate- ISO date or unix timestampendDate- ISO date or unix timestamplimit- max records to return (default 50, max 200)cursor- pagination cursor from previous response
History records use Liftoscript Workouts - a compact, human-readable text format for workouts.
Response:
{
"data": {
"records": [
{
"id": 1,
"text": "2026-03-01T10:00:00Z / program: \"5/3/1\" / dayName: \"Squat Day\" / week: 1 / dayInWeek: 1 / duration: 3600s / exercises: {\n Squat, Barbell / 3x5 185lb / warmup: 1x5 95lb, 1x3 135lb / target: 3x5 185lb 120s\n Leg Press / 3x10 200lb / target: 3x10 200lb 90s\n}"
}
],
"hasMore": false
}
}
When hasMore is true, use the nextCursor value as the cursor parameter in the next request:
{
"data": {
"records": [...],
"hasMore": true,
"nextCursor": 42
}
}
Get a History Record
GET /api/v1/history/:id
Response:
{
"data": {
"id": 1,
"text": "2026-03-01T10:00:00Z / program: \"5/3/1\" / dayName: \"Squat Day\" / exercises: {\n Squat, Barbell / 3x5 185lb\n}"
}
}
Create a History Record
POST /api/v1/history
Content-Type: application/json
{
"text": "2026-03-01T10:00:00Z / program: \"My Program\" / dayName: \"Day 1\" / week: 1 / dayInWeek: 1 / exercises: {\n Squat / 3x5 135lb\n Bench Press / 3x8 95lb\n}"
}
If you include program: "Name", the API looks up that program and links the workout to it - exercises get matched to their program counterparts, and the day name is filled in if you didn't specify one. If the program doesn't exist, you'll get a 400 error.
You can also create workouts without a program reference - just omit the program section.
Response (201):
{
"data": {
"id": 1,
"text": "2026-03-01T10:00:00Z / program: \"My Program\" / dayName: \"Day 1\" / week: 1 / dayInWeek: 1 / exercises: {\n Squat / 3x5 135lb\n Bench Press / 3x8 95lb\n}"
}
}
Update a History Record
PUT /api/v1/history/:id
Content-Type: application/json
{
"text": "2026-03-01T10:00:00Z / exercises: {\n Squat / 3x5 155lb\n}"
}
Response:
{
"data": {
"id": 1,
"text": "2026-03-01T10:00:00Z / exercises: {\n Squat / 3x5 155lb\n}"
}
}
Delete a History Record
DELETE /api/v1/history/:id
Response:
{
"data": {
"deleted": true
}
}
Playground
Simulate a workout without saving anything. Use this to test program logic and progressions before committing.
POST /api/v1/playground
Content-Type: application/json
{
"programText": "# Week 1\n## Day 1\nSquat / 3x5 / 135lb / progress: lp(5lb)",
"day": 1,
"week": 1,
"commands": [
"complete_set(1, 1)",
"complete_set(1, 2)",
"complete_set(1, 3)",
"finish_workout()"
]
}
Only programText is required. day, week, and commands are optional.
Available commands:
complete_set(exercise, set)- mark a set done (1-indexed)change_weight(exercise, set, weight)- e.g.change_weight(1, 1, 185lb)change_reps(exercise, set, reps)change_rpe(exercise, set, rpe)set_state_variable(exercise, name, value)finish_workout()- runs progression scripts, returns updated program text
Response:
{
"data": {
"workout": "2026-03-07T10:00:00Z / exercises: {\n Squat, Barbell / 3x5 135lb / target: 3x5 135lb\n}",
"updatedProgramText": "# Week 1\n## Day 1\nSquat / 3x5 / 140lb / progress: lp(5lb)"
}
}
updatedProgramText is only present when finish_workout() is included in the commands.
Program Stats
Get stats for a program without saving it - workout duration estimates, weekly volume per muscle group, strength vs hypertrophy breakdown.
POST /api/v1/program-stats
Content-Type: application/json
{
"programText": "# Week 1\n## Day 1\nSquat / 3x5 / 135lb\nBench Press / 3x8 / 95lb"
}
Response:
{
"data": {
"days": [
{ "name": "Day 1", "approxMinutes": 25, "workingSets": 6 }
],
"totalWeeklySets": 6,
"strengthSets": 3,
"hypertrophySets": 3,
"muscleGroups": [
{
"muscle": "Quads",
"totalSets": 3,
"strengthSets": 3,
"hypertrophySets": 0,
"frequencyPerWeek": 1,
"exercises": [
{ "name": "Squat", "sets": 3, "isSynergist": false }
]
},
{
"muscle": "Chest",
"totalSets": 3,
"strengthSets": 0,
"hypertrophySets": 3,
"frequencyPerWeek": 1,
"exercises": [
{ "name": "Bench Press", "sets": 3, "isSynergist": false }
]
}
]
}
}
Liftoscript and Liftoscript Workouts
Programs use Liftoscript - a custom DSL for defining workout programs. It's not a standard format, so you'll need to learn the syntax to write valid programs. Check out the built-in program library for examples, or use the MCP server to let an AI assistant write programs for you.
History records use Liftoscript Workouts - a compact text format for workouts. Here's what a record looks like:
2026-03-01T10:00:00Z / program: "5/3/1" / dayName: "Push Day" / week: 1 / dayInWeek: 1 / duration: 3600s / exercises: {
Bench Press, Barbell / 3x5 185lb, 1x3 185lb / warmup: 1x5 95lb, 1x3 135lb / target: 3x5 185lb 120s
Overhead Press / 3x10 95lb / target: 3x10 95lb 60s
}
Error Responses
Errors come back as JSON with a status code, error code, and message:
{
"error": {
"code": "parse_error",
"message": "Failed to parse program",
"details": [
{ "line": 3, "offset": 1, "message": "Unknown exercise: Barbell Curl" }
]
}
}
Common status codes:
401- missing or invalid API key403- no active subscription400- invalid input (wrong program name, can't delete active program, etc.)404- record or program not found422- parse error (invalid Liftoscript or Liftoscript Workouts syntax)