Embed Player Data
Retrieve lesson data for embedded players using JWT token authentication. This endpoint is designed for displaying lessons in embedded iframes on external websites.
For a complete guide on embedding lessons, see Embedding Lessons (Hosted).
Endpoint
GET /api/public/lessons/{lessonId}/player-dataAuthentication
This endpoint requires JWT token authentication. The token must be obtained from the Sign Token endpoint and passed as a query parameter.
Unlike other API endpoints that use Bearer token authentication, this endpoint uses a JWT token in the query string for compatibility with iframe embedding.
URL Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
lessonId | string (UUID) | Yes | The lesson ID to retrieve |
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Yes | JWT token from /api/public/sign-token |
include_metadata | boolean | No | Include lesson metadata (default: false) |
validate_playability | boolean | No | Validate lesson is playable (default: true) |
Request Example
curl 'https://your-domain.com/api/public/lessons/550e8400-e29b-41d4-a716-446655440000/player-data?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...&include_metadata=true'Response
Success Response (200 OK)
Returns the lesson data optimized for the TeachariumPlayer, along with user attributes from the token.
{
"lesson": {
"lesson": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Introduction to JavaScript",
"status": "published",
"variable_definitions": [
{
"name": "userName",
"type": "string",
"initialValue": "Student"
}
],
"widget_settings": {
"overrides": {}
}
},
"sections": [
{
"id": "section-1",
"title": "Getting Started",
"order_index": 0,
"steps": [
{
"id": "step-1",
"title": "Welcome",
"order_index": 0,
"content": {
"content": [],
"root": {}
}
}
]
}
],
"totalSteps": 10,
"totalSections": 3
},
"userAttributes": {
"userId": "user_12345",
"sessionId": "session_abc",
"accountType": "premium"
},
"metadata": {
"title": "Introduction to JavaScript",
"status": "published",
"totalSections": 3,
"totalSteps": 10,
"estimatedDuration": 600
},
"playability": {
"valid": true,
"errors": []
}
}Response Fields
| Field | Type | Description |
|---|---|---|
lesson | object | Complete lesson data formatted for the player |
lesson.lesson | object | Lesson metadata and settings |
lesson.sections | array | Array of sections with steps |
lesson.totalSteps | number | Total number of steps across all sections |
lesson.totalSections | number | Total number of sections |
userAttributes | object | User attributes from the JWT token |
metadata | object | Additional lesson metadata (if include_metadata=true) |
playability | object | Playability validation results (if validate_playability=true) |
Error Responses
400 Bad Request
Invalid request parameters.
{
"error": "Invalid lesson ID"
}Possible causes:
- Missing or invalid lesson ID
- Missing token parameter
- Invalid query parameters
401 Unauthorized
Token authentication failed.
{
"error": "Token verification failed: Token expired"
}Possible causes:
- Token has expired
- Token signature is invalid
- Token was not signed with the correct secret
- Missing
JWT_SECRETenvironment variable
403 Forbidden
Token does not grant access to the requested lesson.
{
"error": "Token does not grant access to this lesson"
}Possible causes:
- The lesson ID in the URL doesn’t match the lesson ID in the token
- Attempting to use a token for a different lesson
404 Not Found
Lesson not found or doesn’t belong to the organization.
{
"error": "Lesson not found or access denied"
}Possible causes:
- Lesson ID doesn’t exist
- Lesson belongs to a different organization than the one in the token
- Lesson has been deleted
422 Unprocessable Entity
Lesson has validation errors that prevent playback (only if validate_playability=true).
{
"error": "Lesson has validation errors that prevent playback"
}Possible causes:
- Lesson has no sections
- Lesson has sections with no steps
- Lesson structure is incomplete
500 Internal Server Error
An unexpected error occurred.
{
"error": "An unexpected error occurred while fetching lesson data"
}Usage Example
JavaScript/TypeScript
async function loadEmbeddedLesson(lessonId, token) {
const url = new URL(
`/api/public/lessons/${lessonId}/player-data`,
'https://your-domain.com'
);
url.searchParams.set('token', token);
url.searchParams.set('include_metadata', 'true');
url.searchParams.set('validate_playability', 'true');
const response = await fetch(url.toString());
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
return await response.json();
}
// Usage
try {
const lessonData = await loadEmbeddedLesson(
'550e8400-e29b-41d4-a716-446655440000',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
);
console.log('Lesson loaded:', lessonData.lesson.lesson.title);
console.log('User ID:', lessonData.userAttributes.userId);
console.log('Total steps:', lessonData.lesson.totalSteps);
} catch (error) {
console.error('Failed to load lesson:', error.message);
}React Hook
import { useState, useEffect } from 'react';
function useEmbeddedLesson(lessonId, token) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!lessonId || !token) {
setError('Missing lesson ID or token');
setLoading(false);
return;
}
async function fetchLesson() {
try {
const url = new URL(
`/api/public/lessons/${lessonId}/player-data`,
'https://your-domain.com'
);
url.searchParams.set('token', token);
url.searchParams.set('include_metadata', 'true');
const response = await fetch(url.toString());
if (!response.ok) {
const error = await response.json();
throw new Error(error.error);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchLesson();
}, [lessonId, token]);
return { data, loading, error };
}
// Usage in component
function EmbeddedLessonPlayer({ lessonId, token }) {
const { data, loading, error } = useEmbeddedLesson(lessonId, token);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!data) return null;
return (
<div>
<h1>{data.lesson.lesson.title}</h1>
<LessonPlayer lessonData={data.lesson} />
</div>
);
}CORS Support
This endpoint includes CORS headers to allow embedding from any origin:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-TypeSecurity Considerations
- Token expiration: Tokens expire based on the timeout set when signing (default 2 hours, max 24 hours)
- Lesson access control: Tokens are tied to specific lessons and cannot be used for other lessons
- Organization isolation: Lessons can only be accessed if they belong to the organization that signed the token
- User attributes: User attributes in the token are read-only and cannot be modified without re-signing
- Service role access: This endpoint uses service role access to bypass RLS, as authentication is handled via JWT
Related Endpoints
- Sign Token - Generate JWT tokens for embedding
- List Lessons - List available lessons for your organization
Differences from Authenticated Player Data
This endpoint differs from /api/lessons/{id}/player-data in several ways:
| Feature | Public Embed Endpoint | Authenticated Endpoint |
|---|---|---|
| Authentication | JWT token in query string | Session-based (cookies) |
| Access Control | Token-based | RLS + user session |
| CORS | Enabled for all origins | Restricted |
| User Context | From token attributes | From authenticated user |
| Use Case | External embedding | Internal application |
Best Practices
- Generate tokens server-side: Never expose API credentials in client code
- Set appropriate timeouts: Match token expiration to expected lesson duration
- Include user identification: Use
userAttributesin tokens for tracking - Handle token expiration: Implement token refresh logic if needed
- Validate responses: Always check response status and handle errors gracefully
- Cache conservatively: Response includes
Cache-Control: no-storeheader