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:
https://iama.live/api/v1Every 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:
Authorization: Bearer 1|Xv9kq2...tokenTokens 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:
{
"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."]
}
}| Status | Meaning |
|---|---|
401 | Missing, invalid or revoked token. |
403 | The content needs premium, or the calendar day is locked. |
404 | Not found, or not published. |
409 | The fitness survey has not been completed yet. Show the survey, then retry. |
422 | Validation failed. See errors. |
429 | Too 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.
{
"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
}
}Allowed values
| Field | Values |
|---|---|
training_mode | calisthenics, gym |
fitness_goal | lose_weight, build_muscle, get_fit |
fitness_level / level | beginner, intermediate, advanced |
muscle | chest, back, shoulders, biceps, triceps, forearms, core, glutes, quads, hamstrings, calves |
bmi_category | underweight, normal, overweight, obese |
subscription.status | free, trialing, active, past_due, canceled, expired |
subscription.plan | monthly, 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 field | Type | Notes |
|---|---|---|
namerequired | string | Up to 255 characters. |
emailrequired | string | Lowercase, unique. |
passwordrequired | string | At least 8 characters. |
password_confirmationrequired | string | Must match password. |
device_namerequired | string | Up to 100 characters, e.g. Pixel 8. |
training_mode | string | calisthenics (default) or gym. |
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"
}'{
"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 field | Type | Notes |
|---|---|---|
emailrequired | string | |
passwordrequired | string | |
device_namerequired | string | Up to 100 characters. |
{
"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.
{ "data": { ...User } }Update profile
PATCH/me
Send only the fields you want to change.
| Body field | Type | Notes |
|---|---|---|
name | string | Up to 255 characters. |
email | string | Lowercase, unique. |
training_mode | string | calisthenics or gym. |
{ "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 field | Type | Notes |
|---|---|---|
height_cmrequired | number | 100 to 250. |
weight_kgrequired | number | 25 to 350. |
fitness_goalrequired | string | lose_weight, build_muscle or get_fit. |
fitness_levelrequired | string | beginner, intermediate or advanced. |
training_moderequired | string | calisthenics or gym. |
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"
}'{ "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.
| Query | Type | Notes |
|---|---|---|
timezone | string | IANA time zone. An unknown zone returns 422. |
| Day field | Type | Notes |
|---|---|---|
date | string | YYYY-MM-DD |
weekday | string | Monday to Sunday. |
is_today | boolean | |
is_locked | boolean | true when the user cannot open this day. |
workout | Workout or null | A workout. On a locked day it is a teaser with no exercises. null when no published workout matches the survey yet. |
curl "https://iama.live/api/v1/calendar?timezone=Asia/Manila" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"{
"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.
| Parameter | Type | Notes |
|---|---|---|
daterequired | path | YYYY-MM-DD, a day in the current week. |
timezone | query | Same as on this week. Send the same value. |
{
"data": {
"date": "2026-09-30",
"weekday": "Wednesday",
"is_today": true,
"is_locked": false,
"workout": { ...Workout with exercises }
}
}| Status | When |
|---|---|
403 | A free user opens a day other than today. The message is "Upgrade to premium to open the whole week." Show the paywall. |
404 | The date is outside the current week, or no workout matches the survey yet. |
409 | The 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.
| Query | Type | Notes |
|---|---|---|
mode | string | calisthenics or gym. |
level | string | beginner, intermediate or advanced. |
muscle | string | Only workouts that train this muscle. See allowed values. |
access | string | unlocked hides workouts the user cannot open. |
page | integer | Defaults to 1. |
curl "https://iama.live/api/v1/workouts?mode=gym&muscle=chest" \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"Recommended
GET/workouts/recommendedPaginated, 20 per page
Workouts that match the survey: the user's training mode and goal, at or below their level, with the closest level first. Returns 409 until the survey is done. Takes the same muscle, access and page query parameters as list workouts.
{
"data": [ ...Workout ],
"links": { ... },
"meta": { ... },
"recommendation": {
"training_mode": "gym",
"goal": "build_muscle",
"level": "intermediate",
"bmi": 23.2,
"bmi_category": "normal",
"has_premium_access": false
}
}recommendation.goal and recommendation.level are what was actually used, after the BMI safety rules. They can differ from the survey answers.
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.
curl https://iama.live/api/v1/workouts/full-body-starter \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"{ "data": { ...Workout with exercises } }Workout logs
History
GET/logsPaginated, 30 per page
The user's completed sessions, newest first.
{
"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 field | Type | Notes |
|---|---|---|
duration_secondsrequired | integer | 1 to 43200 (12 hours). |
workout_id | integer | A published workout's id. |
calories | integer | 0 to 10000. |
effort | integer | 1 to 10. |
notes | string | Up to 500 characters. |
completed_at | datetime | Defaults to now. Cannot be in the future. |
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
}'{ "data": { ...Workout log } }Objects
User
survey is null until the survey is done, and needs_survey is true until then.
{
"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.
{
"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").
{
"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_urlopens an interactive three.js viewer. Load it in a WebView.model_urlis the raw.glbmodel, 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_versionchanges whenever the model is replaced. Use it as the cache key.- When
has_3disfalse, all three arenull.
Workout log
workout is null for a free-form session, or when the workout was deleted later.
{
"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"
}