The Progress API allows the Platzi Viewer to persist and retrieve user progress through courses. Progress data is stored as JSON in the local filesystem.
Get Progress
curl http://localhost:8080/api/progress
Endpoint
Retrieves the saved progress data from progress.json.
Response
Returns the complete progress object as stored in the progress file. The structure is application-defined.
Progress data is a JSON object with application-specific structure. Common patterns include:
Course completion tracking
Video playback positions
Quiz/exercise results
Last accessed timestamps
Example Response
{
"courses" : {
"curso-html-css" : {
"completed" : [
"class-1" ,
"class-2" ,
"class-3"
],
"currentClass" : "class-4" ,
"videoPositions" : {
"class-4" : 145.5
},
"lastAccessed" : "2026-03-07T10:30:00Z"
},
"curso-javascript" : {
"completed" : [],
"currentClass" : "class-1" ,
"videoPositions" : {},
"lastAccessed" : "2026-03-06T14:20:00Z"
}
},
"preferences" : {
"playbackSpeed" : 1.25 ,
"autoplay" : true ,
"theme" : "dark"
}
}
Behavior
If progress.json doesn’t exist or cannot be read, returns an empty object {}.
File Location
Progress is stored at:
{DATA_PATH}/progress.json
Default: {PLATZI_VIEWER_PATH}/progress.json
Configure via environment variable:
export PLATZI_DATA_PATH = / path / to / data
Save Progress
curl -X POST http://localhost:8080/api/progress \
-H "Content-Type: application/json" \
-d '{
"courses": {
"curso-html-css": {
"completed": ["class-1", "class-2"],
"currentClass": "class-3"
}
}
}'
Endpoint
Saves progress data to the filesystem. Completely replaces the existing progress file.
Request Body
Progress data object. Must be valid JSON. The structure is application-defined.
This endpoint overwrites the entire progress file. To update specific fields, first GET the current progress, merge your changes, then POST the complete object.
Response
Success (200)
Returns "saved" on success
Error (400)
Error message describing what went wrong
{
"error" : "progress payload must be a JSON object"
}
Error (413)
Returns "payload_too_large" when exceeding size limit
{
"error" : "payload_too_large"
}
Size Limits
Maximum payload size in bytes (default: 2MB)
Configure via environment variable:
export MAX_PROGRESS_BYTES = 5242880 # 5MB
Validation
The server validates that:
Content-Length is provided and > 0
Payload size ≤ MAX_PROGRESS_BYTES
Body is valid UTF-8 encoded JSON
Parsed JSON is an object (not an array or primitive)
Recommended Structure
While the progress format is flexible, we recommend this structure:
{
"courses" : {
"[courseId]" : {
"completed" : [ "class-1" , "class-2" ],
"currentClass" : "class-3" ,
"currentModule" : "module-1" ,
"videoPositions" : {
"[classId]" : 145.5
},
"quizScores" : {
"[classId]" : 85
},
"lastAccessed" : "2026-03-07T10:30:00Z" ,
"notes" : {
"[classId]" : "Remember to review this concept"
}
}
},
"preferences" : {
"playbackSpeed" : 1.25 ,
"autoplay" : true ,
"theme" : "dark" ,
"subtitles" : true
},
"stats" : {
"totalClassesCompleted" : 234 ,
"totalCoursesStarted" : 12 ,
"totalCoursesCompleted" : 5 ,
"totalWatchTimeMinutes" : 3420
}
}
Course Progress Fields
Array of completed class IDs
ID of the class currently being studied
Map of class ID to playback position in seconds
Map of class ID to quiz score percentage
ISO 8601 timestamp of last access
User notes for specific classes
Usage Examples
Mark Class as Completed
// 1. Get current progress
const response = await fetch ( 'http://localhost:8080/api/progress' );
const progress = await response . json ();
// 2. Update progress
const courseId = 'curso-html-css' ;
const classId = 'class-4' ;
if ( ! progress . courses ) progress . courses = {};
if ( ! progress . courses [ courseId ]) {
progress . courses [ courseId ] = { completed: [] };
}
if ( ! progress . courses [ courseId ]. completed . includes ( classId )) {
progress . courses [ courseId ]. completed . push ( classId );
}
// 3. Save updated progress
await fetch ( 'http://localhost:8080/api/progress' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( progress )
});
Save Video Position
const courseId = 'curso-html-css' ;
const classId = 'class-4' ;
const position = 145.5 ; // seconds
// Get current progress
const response = await fetch ( 'http://localhost:8080/api/progress' );
const progress = await response . json ();
// Initialize structure if needed
if ( ! progress . courses ) progress . courses = {};
if ( ! progress . courses [ courseId ]) progress . courses [ courseId ] = {};
if ( ! progress . courses [ courseId ]. videoPositions ) {
progress . courses [ courseId ]. videoPositions = {};
}
// Update position
progress . courses [ courseId ]. videoPositions [ classId ] = position ;
progress . courses [ courseId ]. lastAccessed = new Date (). toISOString ();
// Save
await fetch ( 'http://localhost:8080/api/progress' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ( progress )
});
Calculate Completion Percentage
const response = await fetch ( 'http://localhost:8080/api/progress' );
const progress = await response . json ();
const courseId = 'curso-html-css' ;
const totalClasses = 32 ; // From course metadata
const completedClasses = progress . courses ?.[ courseId ]?. completed || [];
const percentage = ( completedClasses . length / totalClasses ) * 100 ;
console . log ( `Course completion: ${ percentage . toFixed ( 1 ) } %` );
File Permissions
The server automatically:
Creates the data directory if it doesn’t exist
Creates progress.json on first save
Overwrites existing file with new data
Ensure the server process has write permissions to the data directory.
Error Handling
Common Errors
Status Error Cause Solution 400progress payload must be a JSON objectBody is not a JSON object Ensure body is {}, not [] or primitive 400JSON parse error Malformed JSON Validate JSON syntax 413payload_too_largeExceeds MAX_PROGRESS_BYTES Reduce data size or increase limit
Best Practices
Implement client-side debouncing for frequent updates (e.g., video position)
Cache progress data in memory to reduce read requests
Validate progress data structure before saving
Include timestamps for debugging and conflict resolution
Next Steps
Course APIs Fetch course data to display progress against
Drive Proxy Stream course videos and files