Building a course¶
Courses are SCORM-compatible packages (SCORM 1.2/2004, AICC, xAPI, cmi5, or PDF). PlusPlus delegates hosting and progress tracking to SCORM Cloud, so every package upload kicks off an asynchronous import. The upload uses the same three-call pattern as videos and guides — with one SCORM-specific twist: the package is sent as multipart/form-data to a presigned S3 URL.
Flow at a glance¶
sequenceDiagram
participant Your app
participant PlusPlus API
participant S3 / SCORM Cloud
Your app->>PlusPlus API: POST /courses/uploads/ { name }
PlusPlus API-->>Your app: 201 { upload_id, upload_url, upload_fields, course_id }
Your app->>S3 / SCORM Cloud: POST multipart/form-data (the ZIP)
loop until ready or errored
Your app->>PlusPlus API: GET /courses/uploads/{upload_id}/status/
PlusPlus API-->>Your app: 200 { status }
end
1. Request an upload URL¶
Two modes:
- Create-and-upload — pass
name. PlusPlus creates a hidden course and binds an upload URL to it. - Re-upload — pass
course_id. The new package replaces the active version on that course (SCORM Cloud versions it internally); the course's existingis_hiddenis preserved.
curl -X POST https://acme.plusplus.app/api/v2/courses/uploads/ \
-H "Authorization: Bearer pp_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "name": "Security Fundamentals", "filename": "security-101.zip" }'
{
"upload_id": "upl_scorm_xyz",
"upload_url": "https://uploads.plusplus.app/scorm/…",
"upload_fields": { "key": "…", "policy": "…", "x-amz-signature": "…" },
"course_id": "01HXYC0ND9F3K2M6PXJW1ASQTV",
"status": "awaiting_upload"
}
Unlike the video/guide uploads (a single PUT), the SCORM upload is an S3 presigned POST: the response includes upload_fields you must send alongside the file as form fields.
2. Upload the ZIP as multipart/form-data¶
Include every key from upload_fields, then the file:
curl -X POST "$UPLOAD_URL" \
-F "key=$KEY" \
-F "policy=$POLICY" \
-F "x-amz-signature=$SIG" \
-F "file=@./security-101.zip"
The PlusPlus API isn't in the data path — your file goes straight to storage. SCORM Cloud auto-detects the package type from the contents, so filename is only informational.
3. Poll until ready¶
curl https://acme.plusplus.app/api/v2/courses/uploads/upl_scorm_xyz/status/ \
-H "Authorization: Bearer pp_your_token_here"
status |
Meaning |
|---|---|
awaiting_upload |
URL issued; storage hasn't received the ZIP. |
processing |
Uploaded; SCORM Cloud is importing (30s to a few minutes). |
ready |
Import complete. SCORM-derived metadata (name, duration, version) is populated. |
errored |
Import failed. Upload a new package to retry. |
There's no separate confirm call — PlusPlus detects the upload and triggers the import automatically the first time you poll. Poll every 3–5 seconds.
4. Configure passing and publish¶
Courses created via upload stay hidden after import — a fresh import lands in draft, and you decide when it goes live. Set passing_score (if you want pass/fail tracking the package doesn't define) and unhide:
curl -X PATCH https://acme.plusplus.app/api/v2/courses/01HXYC0ND9F3K2M6PXJW1ASQTV/ \
-H "Authorization: Bearer pp_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "is_hidden": false, "passing_score": 80 }'
Tracking learner progress¶
Assign the course like any content item (see Assignments & completion). SCORM-specific per-learner outcomes — score, completion, SCORM status — are exposed read-only via Course Registrations:
curl "https://acme.plusplus.app/api/v2/course-registrations/?course_id=01HXYC0ND9F3K2M6PXJW1ASQTV" \
-H "Authorization: Bearer pp_your_token_here"
Course registrations are read-only. To assign, complete, drop, or exempt a learner, use the Assignments endpoints.
Common pitfalls¶
- Using PUT instead of POST for the upload. SCORM uploads are an S3 presigned POST with form fields — not the
PUTused for videos/guides. Send everyupload_fieldsentry. - Expecting the course to auto-publish. It stays hidden after import. Flip
is_hiddentofalsewhen ready. - Size limits. The default tenant cap is 900 MB (SCORM Cloud trial accounts cap at 100 MB). Larger packages take longer to import.
- Batch creation. Create empty course records first, then attach packages in a second pass using
course_idmode.