Skip to main content

Lesson Plays API

Update

Updated on 2025-11-25

GET /api/lesson-plays

List all lesson plays for the authenticated client/user, with support for pagination, filtering, and sorting.

Query Parameters

  • clientId: string (required) — The client identifier. If not found, a 500 error is returned.
  • page: number (optional, default: 1) — The page number (1-based).
  • pageSize: number (optional, default: 20, max: 100) — Number of results per page.
  • sortBy: string (optional, default: createdAt) — Field to sort by (e.g., createdAt, score).
  • sortOrder: string (optional, default: desc) — Sort order: asc or desc.
  • userId: string (optional) — Filter by user ID.
  • lessonId: string (optional) — Filter by lesson ID.
  • startAfter: any (optional) — Cursor value for pagination (use the value of the sortBy field from the last item of the previous page).

Headers:

  • Authorization: Bearer <JWT> (ID token)
  • AdminToken: <JWT> (Admin token alternative)
  • RevokableToken: <JWT> (Revokable token alternative)

Response

  • 200 OK:

    Success Response
    {
    "data": [LessonPlay, ...],
    "page": number,
    "pageSize": number,
    "hasMore": boolean,
    "nextPageToken": any
    }
  • 401 Unauthorized: { "error": string }

  • 500 Internal Server Error: { "error": 'Client ID not found' | string }

  • 500 Firestore index required: { "error": 'Firestore index required', details: string }

note

Use nextPageToken as the startAfter value for the next page request.

Filtering and sorting combinations may require Firestore composite indexes. If an index is missing, the error response will include a link to create it.

Each lesson play object includes a lesson property with the full lesson configuration when available.

Each LessonPlay object includes:

  • id: string — Unique ID of this lesson play record
  • clientId: string — ID of the user client (manages a group of users)
  • userId: string (optional) — ID of the user
  • lessonId: string — Lesson ID
  • createdAt: string (ISO date or Firestore timestamp) — Creation date
  • completedAt: string (ISO date or Firestore timestamp, optional, nullable) — Completion date
  • activityTime: number (optional) — How long the user has spent in the activity, in seconds
  • lessonSessionGradeMap: object (optional) — Grade achieved in each session (map of session index to grade)
  • averageGrade: string (optional, nullable) — Average grade of the workout (A+, A, B, C, D, E, F)
  • lessonSessionEngagementLevelMap: object (optional) — Engagement level (0-1) of each session (map of session index to number)
  • averageEngagementLevel: number (optional, nullable) — Average engagement level (0-1)
  • lessonSessionRepCountMap: object (optional) — Rep count of each session (map of session index to number)
  • totalRepCount: number (optional) — Total reps completed
  • totalTargetRepCount: number (optional) — Total target reps
  • lessonSessionSimilarityScoreMap: object (optional) — Similarity score (0-1) of each session (map of session index to number)
  • averagePoseSimilarity: number (optional, nullable) — Average similarity score (0-1)
  • caloriesBurned: number (optional) — Calories burned
  • sessions: LessonPlaySession[] — Details for each session (see below)
  • email: string (optional)
  • displayName: string (optional)
  • photoURL: string (optional)
  • skipCalibration: boolean (optional)
  • skipWarmUp: boolean (optional)
  • skipCoolDown: boolean (optional)
  • locale: string (optional) — One of: en, zh-Hans, zh-Hant
  • lesson: LessonConfig (optional) — Full lesson configuration (populated by API)
  • playbackSpeed: number (optional) — Current playback speed
  • returnUrl: string (optional) — Custom URL to redirect to after lesson play completion

POST /api/lesson-plays

Create a new lesson play for the authenticated client/user.

Headers:

  • Authorization: Bearer <JWT> (ID token required)

Request Body:

Create Lesson Play Request
{
"lessonId": "string (required)",
"userId": "string (optional)",
"email": "string (optional)",
"displayName": "string (optional)",
"photoURL": "string (optional)",
"skipWarmUp": "boolean (optional)",
"skipCoolDown": "boolean (optional)",
"skipCalibration": "boolean (optional)",
"locale": "string (optional)",
"playbackSpeed": number (optional) — Target playback speed,
"returnUrl": "string (optional)" — Custom URL to redirect to after lesson completion
}

Response:

  • 200 OK:

    Create Lesson Play Response
    {
    "lessonPlayId": "string",
    "redirectUrl": "string",
    "createdAt": "ISO date string"
    }
  • 400 Bad Request:

    • { "error": "Invalid email" } — Email format is invalid
    • { "error": "Invalid photoURL" } — Photo URL format is invalid
    • { "error": "Invalid returnUrl" } — Return URL format is invalid
    • { "error": "User ID does not match auth token" } — User ID mismatch with auth token
    • { "error": "User does not exist" } — User ID not found in client users
    • { "error": "Lesson does not exist" } — Lesson ID not found
    • { "error": "Invalid locale" } — Locale is not supported
  • 401 Unauthorized: { "error": string }

  • 500 Internal Server Error: { "error": "Client ID not found" | string }

Create Notes
  • If userId is provided and an ID token is used, the userId must match the token's uid.
  • The userId must exist in the client's user list if provided.
  • The lessonId must reference an existing lesson.
  • Valid locales are: en, zh-Hans, zh-Hant.
  • Returns a redirectUrl in the format /move/lesson/play/{lessonPlayId}.
  • If returnUrl is provided, the user will be redirected to that URL after the lesson play experience is completed instead of the default result page.

GET /api/lesson-plays/[lessonPlayId]

Fetch a lesson play by its ID.

Headers:

  • Authorization: Bearer <JWT> (ID token)
  • AdminToken: <JWT> (Admin token alternative)
  • RevokableToken: <JWT> (Revokable token alternative)

Path Parameters:

  • lessonPlayId: string (required) — The lesson play's unique identifier

Response:

  • 200 OK: { "data": LessonPlay }
  • 404 Not Found: { "error": "LessonPlay not found" }
  • 401 Unauthorized: { "error": "Unauthorized" } — User ID mismatch with auth token
  • 500 Internal Server Error: { "error": "Client ID not found" | "Authentication error" | "Internal Server Error" }
Get By ID Notes
  • If using an ID token, the lesson play's userId must match the token's uid.
  • The response includes a lesson property with the full lesson configuration when available.
  • Session titles are automatically populated from the lesson configuration.

PATCH /api/lesson-plays/[lessonPlayId]

Update a lesson play record. Currently supports updating the returnUrl field.

Headers:

  • Authorization: Bearer <JWT> (ID token required)

Path Parameters:

  • lessonPlayId: string (required) — The lesson play's unique identifier

Request Body:

Update Lesson Play Request
{
"returnUrl": "string (optional)" — Custom URL to redirect to after lesson completion. Set to null to remove.
}

Response:

  • 200 OK:

    Update Lesson Play Response
    {
    "success": true,
    "lessonPlayId": "string",
    "updatedFields": ["returnUrl"]
    }
  • 400 Bad Request:

    • { "error": "Invalid returnUrl" } — Return URL format is invalid
    • { "error": "No valid fields to update" } — Request body contains no valid fields
  • 401 Unauthorized: { "error": "Unauthorized" } — User ID mismatch with auth token

  • 404 Not Found: { "error": "LessonPlay not found" }

  • 500 Internal Server Error: { "error": "Client ID not found" | "Authentication error" | "Internal Server Error" }

PATCH Notes
  • If using an ID token, the lesson play's userId must match the token's uid.
  • The returnUrl can be set to null to remove it.
  • Only the returnUrl field is currently supported for updates.

LessonPlaySession (in sessions array)

Each lesson play contains an array of LessonPlaySession objects. Fields:

  • index: number — Index of the lesson session
  • grade: string (optional) — Grade achieved (A+, A, B, C, D, E, F)
  • engagementLevel: number (optional) — Engagement level (0-1)
  • poseSimilarity: number (optional) — Pose similarity (0-1)
  • repCount: number (optional) — Rep count
  • targetRepCount: number (optional) — Target rep count
  • title: object (optional) — Multilingual title (populated by API, see below)
  • skipped: boolean (optional) — Whether the session was skipped
Rep Count Note

If either repCount or targetRepCount is provided, both must be provided together.

Multilingual Title Example

Multilingual Title
{
"en": "Warmup",
"zh-Hant": "熱身",
"zh-Hans": "热身"
}

Sample LessonPlay Object

Sample LessonPlay Object
{
"id": "abc123",
"clientId": "gofa",
"userId": "user456",
"lessonId": "lesson789",
"createdAt": "2024-05-24T10:00:00.000Z",
"completedAt": null,
"activityTime": 1200,
"lessonSessionGradeMap": { "0": "A", "1": "B" },
"averageGrade": "A",
"lessonSessionEngagementLevelMap": { "0": 0.95, "1": 0.8 },
"averageEngagementLevel": 0.875,
"lessonSessionRepCountMap": { "0": 10, "1": 15 },
"totalRepCount": 25,
"totalTargetRepCount": 30,
"lessonSessionSimilarityScoreMap": { "0": 0.9, "1": 0.85 },
"averagePoseSimilarity": 0.875,
"caloriesBurned": 100,
"sessions": [
{
"index": 0,
"grade": "A",
"engagementLevel": 0.95,
"poseSimilarity": 0.9,
"repCount": 10,
"targetRepCount": 12,
"title": { "en": "Warmup", "zh-Hant": "熱身", "zh-Hans": "热身" },
"skipped": false
},
{
"index": 1,
"grade": "B",
"engagementLevel": 0.8,
"poseSimilarity": 0.85,
"repCount": 15,
"targetRepCount": 18,
"title": { "en": "Workout", "zh-Hant": "運動", "zh-Hans": "运动" },
"skipped": false
}
],
"email": "user@example.com",
"displayName": "User Name",
"photoURL": "https://example.com/photo.jpg",
"skipCalibration": false,
"skipWarmUp": false,
"skipCoolDown": false,
"locale": "en",
"returnUrl": "https://example.com/custom-result-page",
"lesson": {
"id": "lesson789",
"title": { "en": "Pure Yoga", "zh-Hant": "純瑜伽", "zh-Hans": "纯瑜伽" },
"workoutCategory": "ENGAGEMENT"
}
}

Schema Notes
  • All fields may not be present on every lesson play. Optional and nullable fields are indicated above.
  • For the most up-to-date schema, refer to the lesson play and session models/interfaces in your codebase (src/models/lesson-play.ts and src/models/lesson-play-session.ts).
  • Authentication can be done using ID tokens (Bearer), Admin tokens, or Revokable tokens depending on the endpoint.
  • The API automatically populates session titles and lesson configuration data when fetching lesson plays.