I am Alive! API

The JSON API used by the I am Alive! mobile app: accounts, the fitness survey, the weekly workout calendar, the workout library, and workout history.

Overview

All endpoints live under one base URL:

Base URL
https://iama.live/api/v1

Every request should send Accept: application/json. Request bodies are JSON (Content-Type: application/json). All timestamps are ISO 8601 and dates are YYYY-MM-DD.

Heights and weights are always metric (cm, kg). The app converts imperial input before sending it.

Authentication

Register or log in to get a token, then send it with every other request:

Header
Authorization: Bearer 1|Xv9kq2...token

Tokens do not expire on their own. Each device gets its own token (named with device_name), and logging out revokes only the token used for that request. A missing or revoked token returns 401.

Rate limits

  • Register and log in: 6 requests per minute.
  • Everything else: 120 requests per minute per user.

Going over the limit returns 429 with a Retry-After header in seconds.

Errors

Errors are JSON with a message. Validation errors (422) also list the problems per field:

422 Unprocessable Content
{
  "message": "The email field is required. (and 1 more error)",
  "errors": {
    "email": ["The email field is required."],
    "device_name": ["The device name field is required."]
  }
}
StatusMeaning
401Missing, invalid or revoked token.
403The content needs premium, or the calendar day is locked.
404Not found, or not published.
409The fitness survey has not been completed yet. Show the survey, then retry.
422Validation failed. See errors.
429Too many requests. Wait for Retry-After seconds.

Pagination

List endpoints return a page of results in data, with links and meta for paging. Pass ?page=2 to get the next page, or just follow links.next until it is null.

Paginated response
{
  "data": [ ... ],
  "links": {
    "first": "https://iama.live/api/v1/workouts?page=1",
    "last": "https://iama.live/api/v1/workouts?page=3",
    "prev": null,
    "next": "https://iama.live/api/v1/workouts?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 3,
    "path": "https://iama.live/api/v1/workouts",
    "per_page": 20,
    "to": 20,
    "total": 47
  }
}

Free and premium

A user has premium when subscription.has_premium_access is true on the user. Subscriptions are bought in the app through the App Store and Google Play.

  • Calendar: free users can open today's workout only. Premium users can open every day of the current week.
  • Premium workouts: free users see them with is_locked: true. A locked workout has its title, duration, target muscles and exercise count, but no description or exercises. Use it as a paywall teaser.

Allowed values

FieldValues
training_modecalisthenics, gym
fitness_goallose_weight, build_muscle, get_fit
fitness_level / levelbeginner, intermediate, advanced
musclechest, back, shoulders, biceps, triceps, forearms, core, glutes, quads, hamstrings, calves
bmi_categoryunderweight, normal, overweight, obese
subscription.statusfree, trialing, active, past_due, canceled, expired
subscription.planmonthly, quarterly, semiannual, or null

Auth

Register

POST/auth/registerNo token

Creates an account and returns a token. After registering, check user.needs_survey and show the fitness survey.

Body fieldTypeNotes
namerequiredstringUp to 255 characters.
emailrequiredstringLowercase, unique.
passwordrequiredstringAt least 8 characters.
password_confirmationrequiredstringMust match password.
device_namerequiredstringUp to 100 characters, e.g. Pixel 8.
training_modestringcalisthenics (default) or gym.
Request
curl -X POST https://iama.live/api/v1/auth/register \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Alex Cruz",
    "email": "alex@example.com",
    "password": "secret-pass-1",
    "password_confirmation": "secret-pass-1",
    "device_name": "Pixel 8"
  }'
201 Created
{
  "token": "1|Xv9kq2...",
  "token_type": "Bearer",
  "user": { ...User }
}

Log in

POST/auth/loginNo token

Exchanges an email and password for a new token. Wrong credentials return 422 with the error on email.

Body fieldTypeNotes
emailrequiredstring
passwordrequiredstring
device_namerequiredstringUp to 100 characters.
200 OK
{
  "token": "2|p0Lm7a...",
  "token_type": "Bearer",
  "user": { ...User }
}

Log out

POST/auth/logout

Revokes the token used for this request. Returns 204 No Content.

Profile

Current user

GET/me

Returns the signed-in user, including survey answers and subscription state. Call it on app start to decide whether to show the survey or the paywall.

200 OK
{ "data": { ...User } }

Update profile

PATCH/me

Send only the fields you want to change.

Body fieldTypeNotes
namestringUp to 255 characters.
emailstringLowercase, unique.
training_modestringcalisthenics or gym.
200 OK
{ "data": { ...User } }

Fitness survey

PUT/me/survey

Saves the first-run fitness survey. The calendar and recommendations are picked from these answers. It can be retaken at any time, and the calendar updates straight away.

Body fieldTypeNotes
height_cmrequirednumber100 to 250.
weight_kgrequirednumber25 to 350.
fitness_goalrequiredstringlose_weight, build_muscle or get_fit.
fitness_levelrequiredstringbeginner, intermediate or advanced.
training_moderequiredstringcalisthenics or gym.
Request
curl -X PUT https://iama.live/api/v1/me/survey \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "height_cm": 172,
    "weight_kg": 68.5,
    "fitness_goal": "build_muscle",
    "fitness_level": "intermediate",
    "training_mode": "gym"
  }'
200 OK
{ "data": { ...User } }

Two safety rules apply when picking workouts. A user who is underweight and asks to lose weight gets get_fit workouts instead. A user whose BMI is obese gets beginner workouts, whatever level they picked.

Calendar

The app's home screen is a weekly calendar with one workout per day, picked from the fitness survey. Weeks run Monday to Sunday. Each date always gets the same workout, and consecutive days rotate through the user's matching workouts.

Send the device's time zone as timezone (an IANA name such as Asia/Manila) so "today" matches the user's clock. Without it, the server uses UTC.

Both endpoints return 409 until the survey is done.

This week

GET/calendar

Returns all seven days of the current week. Free users can open today only. The other days come back with is_locked: true and a teaser workout, so the app can show what premium unlocks. Premium users get every day unlocked.

QueryTypeNotes
timezonestringIANA time zone. An unknown zone returns 422.
Day fieldTypeNotes
datestringYYYY-MM-DD
weekdaystringMonday to Sunday.
is_todayboolean
is_lockedbooleantrue when the user cannot open this day.
workoutWorkout or nullA workout. On a locked day it is a teaser with no exercises. null when no published workout matches the survey yet.
Request
curl "https://iama.live/api/v1/calendar?timezone=Asia/Manila" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
200 OK (free user, Wednesday)
{
  "data": [
    {
      "date": "2026-09-28",
      "weekday": "Monday",
      "is_today": false,
      "is_locked": true,
      "workout": {
        "id": 4,
        "slug": "upper-body-builder",
        "title": "Upper Body Builder",
        "summary": "Push and pull supersets for chest, back and arms.",
        "mode": "gym",
        "level": "intermediate",
        "goals": ["build_muscle"],
        "duration_minutes": 40,
        "target_muscles": ["chest", "back", "biceps", "triceps"],
        "exercise_count": 5,
        "is_premium": false,
        "is_locked": true,
        "updated_at": "2026-09-27T11:02:14+00:00"
      }
    },
    ...
    {
      "date": "2026-09-30",
      "weekday": "Wednesday",
      "is_today": true,
      "is_locked": false,
      "workout": { ...Workout with exercises }
    },
    ...
  ],
  "meta": {
    "timezone": "Asia/Manila",
    "today": "2026-09-30",
    "week_start": "2026-09-28",
    "week_end": "2026-10-04",
    "has_premium_access": false
  }
}

Open a day

GET/calendar/{date}

Opens one day of the current week, with the full workout and its exercises. Call it when the user taps a day.

ParameterTypeNotes
daterequiredpathYYYY-MM-DD, a day in the current week.
timezonequerySame as on this week. Send the same value.
200 OK
{
  "data": {
    "date": "2026-09-30",
    "weekday": "Wednesday",
    "is_today": true,
    "is_locked": false,
    "workout": { ...Workout with exercises }
  }
}
StatusWhen
403A free user opens a day other than today. The message is "Upgrade to premium to open the whole week." Show the paywall.
404The date is outside the current week, or no workout matches the survey yet.
409The survey has not been completed.

Workouts

The workout library. Only published workouts are returned.

List workouts

GET/workoutsPaginated, 20 per page

All published workouts, sorted by mode, then duration. Premium workouts are included but locked for free users.

QueryTypeNotes
modestringcalisthenics or gym.
levelstringbeginner, intermediate or advanced.
musclestringOnly workouts that train this muscle. See allowed values.
accessstringunlocked hides workouts the user cannot open.
pageintegerDefaults to 1.
Request
curl "https://iama.live/api/v1/workouts?mode=gym&muscle=chest" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"

Get a workout

GET/workouts/{slug}

A single workout with its exercises. A free user opening a premium workout gets 403, and an unknown or unpublished workout returns 404.

Request
curl https://iama.live/api/v1/workouts/full-body-starter \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
200 OK
{ "data": { ...Workout with exercises } }

Workout logs

History

GET/logsPaginated, 30 per page

The user's completed sessions, newest first.

200 OK
{
  "data": [ ...Workout log ],
  "links": { ... },
  "meta": { ... }
}

Log a session

POST/logs

Records a finished session. workout_id is optional so free-form sessions can be logged too. Logging a premium workout without premium returns 422 on workout_id.

Body fieldTypeNotes
duration_secondsrequiredinteger1 to 43200 (12 hours).
workout_idintegerA published workout's id.
caloriesinteger0 to 10000.
effortinteger1 to 10.
notesstringUp to 500 characters.
completed_atdatetimeDefaults to now. Cannot be in the future.
Request
curl -X POST https://iama.live/api/v1/logs \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "workout_id": 4,
    "duration_seconds": 2460,
    "calories": 310,
    "effort": 7
  }'
201 Created
{ "data": { ...Workout log } }

Objects

User

survey is null until the survey is done, and needs_survey is true until then.

User
{
  "id": 42,
  "name": "Alex Cruz",
  "email": "alex@example.com",
  "training_mode": "gym",
  "needs_survey": false,
  "survey": {
    "height_cm": 172.0,
    "weight_kg": 68.5,
    "bmi": 23.2,
    "bmi_category": "normal",
    "fitness_goal": "build_muscle",
    "fitness_level": "intermediate",
    "completed_at": "2026-09-28T09:41:03+00:00"
  },
  "subscription": {
    "status": "active",
    "plan": "monthly",
    "ends_at": "2026-10-28T09:45:00+00:00",
    "has_premium_access": true
  },
  "created_at": "2026-09-28T09:40:12+00:00"
}

Use subscription.has_premium_access to decide what to unlock, not status. It also accounts for ends_at, and a past_due subscription still has access.

Workout

When is_locked is true, description and exercises are left out. target_muscles are listed head to toe.

Workout
{
  "id": 7,
  "slug": "full-body-starter",
  "title": "Full Body Starter",
  "summary": "A gentle full-body session to build the habit.",
  "description": "Three rounds of simple compound moves...",
  "mode": "gym",
  "level": "beginner",
  "goals": ["get_fit", "lose_weight"],
  "duration_minutes": 30,
  "target_muscles": ["chest", "core", "glutes", "quads"],
  "exercise_count": 3,
  "exercises": [ ...Exercise ],
  "is_premium": false,
  "is_locked": false,
  "updated_at": "2026-09-27T11:02:14+00:00"
}

Exercise

An exercise as it appears inside a workout, in order. reps is a string because it can be a count ("12"), a range ("8-10") or a time ("30s").

Exercise
{
  "id": 3,
  "slug": "push-up",
  "name": "Push-up",
  "instructions": "Keep your body in a straight line...",
  "target_muscles": ["chest", "triceps"],
  "sets": 3,
  "reps": "12",
  "rest_seconds": 60,
  "has_3d": true,
  "viewer_url": "https://iama.live/viewer/exercises/push-up?expires=...&signature=...",
  "model_url": "https://iama.live/viewer/exercises/push-up/model.glb?expires=...&signature=...",
  "model_version": 1790500934
}

3D demos

  • viewer_url opens an interactive three.js viewer. Load it in a WebView.
  • model_url is the raw .glb model, for apps that render it themselves or cache it.
  • Both links are signed and expire after about 2 hours. Fetch the workout again for fresh links.
  • model_version changes whenever the model is replaced. Use it as the cache key.
  • When has_3d is false, all three are null.

Workout log

workout is null for a free-form session, or when the workout was deleted later.

Workout log
{
  "id": 118,
  "workout": {
    "id": 4,
    "slug": "upper-body-builder",
    "title": "Upper Body Builder",
    "mode": "gym"
  },
  "duration_seconds": 2460,
  "calories": 310,
  "effort": 7,
  "notes": null,
  "completed_at": "2026-09-30T07:15:00+00:00"
}