Skip to main content
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

GET /api/progress
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.
[key]
any
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

POST /api/progress
Saves progress data to the filesystem. Completely replaces the existing progress file.

Request Body

progress
object
required
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)
status
string
Returns "saved" on success
{
  "status": "saved"
}
Error (400)
error
string
Error message describing what went wrong
{
  "error": "progress payload must be a JSON object"
}
Error (413)
error
string
Returns "payload_too_large" when exceeding size limit
{
  "error": "payload_too_large"
}

Size Limits

MAX_PROGRESS_BYTES
number
default:"2097152"
Maximum payload size in bytes (default: 2MB)
Configure via environment variable:
export MAX_PROGRESS_BYTES=5242880  # 5MB

Validation

The server validates that:
  1. Content-Length is provided and > 0
  2. Payload size ≤ MAX_PROGRESS_BYTES
  3. Body is valid UTF-8 encoded JSON
  4. Parsed JSON is an object (not an array or primitive)

Progress Data Format

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

completed
array
Array of completed class IDs
currentClass
string
ID of the class currently being studied
currentModule
string
ID of the current module
videoPositions
object
Map of class ID to playback position in seconds
quizScores
object
Map of class ID to quiz score percentage
lastAccessed
string
ISO 8601 timestamp of last access
notes
object
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:
  1. Creates the data directory if it doesn’t exist
  2. Creates progress.json on first save
  3. Overwrites existing file with new data
Ensure the server process has write permissions to the data directory.

Error Handling

Common Errors

StatusErrorCauseSolution
400progress payload must be a JSON objectBody is not a JSON objectEnsure body is {}, not [] or primitive
400JSON parse errorMalformed JSONValidate JSON syntax
413payload_too_largeExceeds MAX_PROGRESS_BYTESReduce 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

Build docs developers (and LLMs) love