H5P Content API Reference
All H5P endpoints are mounted under the /h5p prefix on the Express server (see server/index.ts line 162). The @lumieducation/h5p-express library also serves built-in Ajax routes at the same prefix for core, editor, library, content, and temp file requests.
Base URL: /h5p
Table of Contents
- Authentication
- Content CRUD
- Editor Endpoints
- Player Endpoint
- Visibility
- Export
- Import
- Media Management
- Quality Assessment
- Library Management
- Static File Routes
- Health Check
- Content Ownership Model
- Visibility States
- File Upload Limits
- Soft Delete Behavior
Authentication
H5P endpoints use two authentication levels:
| Middleware | Description | Applies to |
|---|---|---|
authMiddleware | Requires a valid JWT (Bearer token in Authorization header or token query parameter). Any authenticated user. | Read-only endpoints (get, play, list libraries, content types) |
teacherAuthMiddleware | Requires valid JWT and teacher role. | Write endpoints (create, update, delete, editor, import, install libraries) |
Token can be passed as:
Authorization: Bearer <token>header?token=<token>query parameter (used for H5P iframe embedding)
Content CRUD
List Content
Returns all H5P content owned by the authenticated user, enriched with curriculum metadata, quality scores, visibility, and view counts.
GET /h5p/content
Auth: teacherAuthMiddleware (teacher role required)
Response:
{
"success": true,
"data": [
{
"id": "123456789",
"title": "Photosynthesis Quiz",
"mainLibrary": "H5P.MultiChoice 1.16",
"contentTypeSlug": "multiple-choice",
"createdAt": "2025-03-15T10:00:00.000Z",
"updatedAt": "2025-03-20T14:30:00.000Z",
"embedTypes": ["iframe"],
"curriculum": {
"frameworkName": "NGSS",
"stageName": "Middle School",
"gradeName": "Grade 7",
"subjectName": "Biology",
"topicNames": ["Photosynthesis"],
"difficulty": "intermediate",
"language": "en"
},
"quality": {
"overallScore": 85,
"bloomsLevel": "Analyze"
},
"visibility": "public",
"viewCount": 42
}
]
}
Notes:
- Only returns content where the current user is the recorded owner in the
content_ownershiptable. curriculum,quality,visibility, andviewCountare batch-enriched; if any lookup fails the field falls back tonull,"private", or0.contentTypeSlugis derived from themainLibraryusing a hardcoded mapping (e.g.H5P.MultiChoicebecomesmultiple-choice). Unmapped types return"unknown".
Get Content
Returns metadata for a single content item.
GET /h5p/content/:contentId
Auth: authMiddleware (any authenticated user)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID |
Response:
{
"success": true,
"data": {
"id": "123456789",
"title": "Photosynthesis Quiz",
"mainLibrary": "H5P.MultiChoice 1.16",
"createdAt": "2025-03-15T10:00:00.000Z",
"updatedAt": "2025-03-20T14:30:00.000Z",
"embedTypes": ["iframe"]
}
}
Create Content
Creates new H5P content. Checks the user's subscription content creation quota before saving.
POST /h5p/content
Auth: teacherAuthMiddleware (teacher role required)
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
library | string | Yes | Library identifier, format: "H5P.LibraryName majorVersion.minorVersion" (e.g. "H5P.MultiChoice 1.16") |
params | object or string | Yes | Content parameters (H5P content JSON). Can be a JSON string or object. |
metadata | object | No | Content metadata overrides |
metadata.title | string | No | Content title (default: "Untitled Content") |
metadata.embedTypes | string[] | No | Embed types (default: ["iframe"]) |
metadata.language | string | No | Content language (default: "und") |
metadata.defaultLanguage | string | No | Default language (default: "en") |
metadata.license | string | No | License type (default: "U" for undisclosed) |
tempImageFiles | string[] | No | Filenames of AI-generated images in temp storage to attach |
Request Example:
{
"library": "H5P.MultiChoice 1.16",
"params": {
"question": "What is photosynthesis?",
"answers": [
{ "text": "Process of converting light to energy", "correct": true },
{ "text": "Process of breathing", "correct": false }
]
},
"metadata": {
"title": "Photosynthesis Quiz"
}
}
Response (201):
{
"success": true,
"data": {
"contentId": "987654321",
"metadata": {
"title": "Photosynthesis Quiz",
"mainLibrary": "H5P.MultiChoice",
"embedTypes": ["iframe"],
"language": "und",
"defaultLanguage": "en",
"license": "U",
"preloadedDependencies": [
{ "machineName": "H5P.MultiChoice", "majorVersion": 1, "minorVersion": 16 }
]
}
},
"message": "Content created successfully"
}
Error Responses:
| Status | Condition |
|---|---|
| 400 | Missing params or library; invalid library format; invalid JSON in params |
| 429 | Subscription content creation quota exceeded |
Update Content
Updates an existing H5P content item. Only the content owner can update.
PUT /h5p/content/:contentId
Auth: teacherAuthMiddleware (teacher role required)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID |
Request Body: Same as Create Content (library, params, metadata).
Response:
{
"success": true,
"data": {
"contentId": "987654321",
"metadata": {
"title": "Updated Quiz Title",
"mainLibrary": "H5P.MultiChoice",
"embedTypes": ["iframe"],
"language": "und",
"defaultLanguage": "en",
"license": "U",
"preloadedDependencies": [
{ "machineName": "H5P.MultiChoice", "majorVersion": 1, "minorVersion": 16 }
]
}
},
"message": "Content updated successfully"
}
Error Responses:
| Status | Condition |
|---|---|
| 400 | Missing params or library; invalid library format |
| 403 | User is not the content owner |
Ownership Note: If no ownership record exists (legacy content), the update is allowed and ownership is recorded for the requesting user.
Delete Content
Deletes an H5P content item and its ownership record.
DELETE /h5p/content/:contentId
Auth: teacherAuthMiddleware (teacher role required)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID |
Response:
{
"success": true,
"message": "Content deleted successfully"
}
Error Responses:
| Status | Condition |
|---|---|
| 403 | User is not the content owner |
Notes:
- Deletes both the H5P content files (via
h5pEditor.deleteContent) and thecontent_ownershipdatabase row. - Legacy content with no ownership record can be deleted by any teacher.
Editor Endpoints
These endpoints return the H5P editor model object (scripts, styles, integration config) needed by @lumieducation/h5p-react components on the frontend.
New Editor Model
Returns the editor model for creating new content.
GET /h5p/new
Auth: teacherAuthMiddleware (teacher role required)
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
language | string | "en" | Editor UI language |
token | string | - | JWT token (injected into model AJAX URLs for iframe auth) |
Response:
{
"success": true,
"data": {
"integration": {
"ajaxPath": "/h5p/ajax?token=...&action=",
"ajax": { "setFinished": "...", "contentUserData": "..." },
"editor": { "ajaxPath": "...", "contentLanguage": "en" },
"contentLanguage": "en"
},
"scripts": ["/h5p/core/js/h5p.js", "..."],
"styles": ["/h5p/core/styles/h5p.css", "..."],
"contentLanguage": "en"
}
}
Edit Editor Model
Returns the editor model pre-populated with existing content for editing.
GET /h5p/edit/:contentId
Auth: teacherAuthMiddleware (teacher role required)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID to edit |
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
language | string | "en" | Editor UI language |
token | string | - | JWT token (injected into model AJAX URLs) |
Response:
The response includes the same editor model as /h5p/new plus three additional fields:
{
"success": true,
"data": {
"integration": { "..." : "..." },
"scripts": ["..."],
"styles": ["..."],
"library": "H5P.MultiChoice 1.16",
"params": { "question": "...", "answers": ["..."] },
"metadata": {
"title": "Photosynthesis Quiz",
"mainLibrary": "H5P.MultiChoice",
"preloadedDependencies": ["..."]
}
}
}
| Extra Field | Description |
|---|---|
library | Full library string with version (e.g. "H5P.MultiChoice 1.16") |
params | Parsed content.json (the content parameters) |
metadata | Parsed h5p.json (content metadata including dependencies) |
Player Endpoint
Play Content
Returns the H5P player model for rendering content to learners.
GET /h5p/play/:contentId
Auth: authMiddleware (any authenticated user)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID to play |
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
language | string | "en" | Player UI language |
token | string | - | JWT token (injected into model AJAX URLs) |
Response:
{
"success": true,
"data": {
"integration": { "..." : "..." },
"scripts": ["/h5p/core/js/h5p.js", "..."],
"styles": ["/h5p/core/styles/h5p.css", "..."]
}
}
Side Effect: Increments the view_count in the content ownership record (fire-and-forget, does not block the response).
Visibility
Toggle Visibility
Changes content visibility between private and public. Only the content owner can toggle.
PATCH /h5p/content/:contentId/visibility
Auth: teacherAuthMiddleware (teacher role required)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID |
Request Body:
{
"visibility": "public"
}
| Field | Type | Required | Values |
|---|---|---|---|
visibility | string | Yes | "private" or "public" |
Response:
{
"success": true,
"data": {
"contentId": "123456789",
"visibility": "public"
}
}
Error Responses:
| Status | Condition |
|---|---|
| 400 | Missing or invalid visibility value |
| 401 | User not found in database |
| 403 | User is not the content owner |
Export
Export as H5P Package
Downloads the content as a .h5p file (ZIP archive containing content.json, h5p.json, and dependencies).
GET /h5p/content/:contentId/export
Auth: teacherAuthMiddleware (teacher role required)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID |
Response:
- Content-Type:
application/zip - Content-Disposition:
attachment; filename="<title>.h5p" - Body: binary ZIP stream
Export as SCORM Package
Downloads the content as a SCORM-compliant ZIP archive. Requires the scorm-export feature flag to be enabled for the user's subscription.
GET /h5p/content/:contentId/export-scorm
Auth: authMiddleware (any authenticated user) + requireFeature('scorm-export') gate
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID |
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
version | string | "1.2" | SCORM version. Accepted values: "1.2" or "2004" |
Response:
- Content-Type:
application/zip - Content-Disposition:
attachment; filename="<title>_scorm<version>.zip" - Body: binary ZIP stream
SCORM Package Contents:
| File | Description |
|---|---|
imsmanifest.xml | SCORM manifest (1.2 or 2004 schema) |
index.html | Launcher HTML page |
h5p-scorm-wrapper.js | SCORM API wrapper (cmi.core for 1.2, cmi for 2004) |
content.json | H5P content parameters |
images/, videos/, audios/, files/ | Media subdirectories (if present) |
Import
Import H5P Package
Uploads and imports a .h5p package file. Validates ZIP magic bytes before processing.
POST /h5p/import
Auth: teacherAuthMiddleware (teacher role required)
Content-Type: multipart/form-data
Form Fields:
| Field | Type | Required | Description |
|---|---|---|---|
h5p | file | Yes | The .h5p package file |
File Constraints:
- Maximum size: 500 MB
- Accepted MIME types:
application/zip,application/x-zip-compressed,application/octet-stream, or any file with.h5pextension - Must have valid ZIP magic bytes (
PK\x03\x04)
Response (201):
{
"success": true,
"data": {
"contentId": "987654321",
"installedLibraries": 5
},
"message": "H5P package imported successfully"
}
Error Responses:
| Status | Condition |
|---|---|
| 400 | No file uploaded; not a valid ZIP archive; package contains only libraries (no content) |
Media Management
Upload Media File
Uploads a base64-encoded media file to temporary storage. Used for AI-generated images and other media that need to be attached to content later.
POST /h5p/media/upload
Auth: authMiddleware (any authenticated user)
Request Body:
{
"filename": "ai-generated-diagram.png",
"data": "<base64-encoded-file-data>",
"contentType": "image/png"
}
| Field | Type | Required | Description |
|---|---|---|---|
filename | string | Yes | Target filename |
data | string | Yes | Base64-encoded file data |
contentType | string | No | MIME type (default: "image/png") |
Response (201):
{
"success": true,
"data": {
"url": "/h5p/temp/uploads/ai-generated-diagram.png",
"filename": "ai-generated-diagram.png",
"size": 45678,
"contentType": "image/png"
}
}
Attach Media to Content
Copies uploaded media files from temporary storage into a content item's media directory.
POST /h5p/media/attach
Auth: authMiddleware (any authenticated user)
Request Body:
{
"contentId": "987654321",
"files": ["diagram.png", "chart.png"],
"mediaType": "images"
}
| Field | Type | Required | Description |
|---|---|---|---|
contentId | string | Yes | Target H5P content ID |
files | string[] | Yes | Array of filenames (must exist in temp uploads) |
mediaType | string | No | Subfolder type: "images", "videos", or "audios" (default: "images") |
Response:
{
"success": true,
"data": {
"contentId": "987654321",
"attached": ["diagram.png", "chart.png"],
"count": 2,
"mediaType": "images"
}
}
Notes:
- Files are copied from
h5p/temp/uploads/toh5p/content/<contentId>/<mediaType>/. - Files not found in temp storage are silently skipped.
Quality Assessment
Get Quality Report
Returns an AI-generated quality assessment for the content. Results are cached in a quality.json file alongside the content. If no cached report exists, one is generated on the fly using the Creatiq AI quality critic.
GET /h5p/content/:contentId/quality
Auth: authMiddleware (any authenticated user)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
contentId | string | H5P content ID |
Response:
{
"success": true,
"data": {
"contentId": "987654321",
"qualityReport": {
"overallScore": 85,
"bloomsLevel": "Analyze",
"strengths": ["Clear question stem", "Good distractors"],
"improvements": ["Add feedback for incorrect answers"],
"accessibilityScore": 90
},
"generatedAt": "2025-03-20T14:30:00.000Z"
}
}
Notes:
- Cached reports are returned immediately. If the cache file is corrupted, it is deleted and regenerated.
- Generation requires the content to have valid
content.jsonandh5p.jsonfiles.
Library Management
List Content Types
Returns the H5P Hub content type cache (available content types that can be installed).
GET /h5p/content-types
Auth: authMiddleware (any authenticated user)
Response:
{
"success": true,
"data": {
"contentTypes": [
{
"id": "H5P.MultiChoice",
"title": "Multiple Choice",
"description": "Create flexible multiple choice questions",
"majorVersion": 1,
"minorVersion": 16
}
]
}
}
List Installed Libraries
Returns all H5P libraries currently installed on the server.
GET /h5p/libraries
Auth: authMiddleware (any authenticated user)
Response:
{
"success": true,
"data": [
{
"machineName": "H5P.MultiChoice",
"versions": ["1.16.10", "1.16.11"],
"latestVersion": "1.16.11"
}
]
}
Install Library from Hub
Installs a single library from the H5P Hub by machine name.
POST /h5p/libraries/install
Auth: teacherAuthMiddleware (teacher role required)
Request Body:
{
"machineName": "H5P.MultiChoice"
}
| Field | Type | Required | Description |
|---|---|---|---|
machineName | string | Yes | H5P library machine name (e.g. "H5P.MultiChoice") |
Response:
{
"success": true,
"message": "Library H5P.MultiChoice installed successfully"
}
Install Essential Libraries
Batch-installs all essential content types defined in the platform configuration. Updates the content type cache first.
POST /h5p/libraries/install-essentials
Auth: teacherAuthMiddleware (teacher role required)
Response:
{
"success": true,
"message": "Installed 28/30 content types",
"data": {
"total": 30,
"installed": 28,
"failed": 2,
"results": [
{ "name": "H5P.MultiChoice", "success": true },
{ "name": "H5P.TrueFalse", "success": true },
{ "name": "H5P.SomeType", "success": false, "error": "Not found in hub" }
]
}
}
Essential Content Types:
H5P.MultiChoice, H5P.TrueFalse, H5P.Blanks, H5P.DragText, H5P.DragQuestion, H5P.MarkTheWords, H5P.QuestionSet, H5P.InteractiveVideo, H5P.CoursePresentation, H5P.InteractiveBook, H5P.BranchingScenario, H5P.Timeline, H5P.ImageHotspots, H5P.MemoryGame, H5P.Flashcards, H5P.DialogCards, H5P.Accordion, H5P.Column, H5P.ImageSlider, H5P.Summary, H5P.SingleChoiceSet, H5P.Essay, H5P.SortParagraphs, H5P.ImageSequencing, H5P.FindTheWords, H5P.Crossword, H5P.ArithmeticQuiz, H5P.GuessTheAnswer, H5P.ImageJuxtaposition, H5P.Chart
Library Status
Returns installation status for all essential content types.
GET /h5p/libraries/status
Auth: authMiddleware (any authenticated user)
Response:
{
"success": true,
"data": {
"total": 30,
"installed": 25,
"ready": true,
"status": [
{ "name": "H5P.MultiChoice", "installed": true },
{ "name": "H5P.TrueFalse", "installed": true },
{ "name": "H5P.SomeRareType", "installed": false }
]
}
}
Note: ready is true when at least 5 essential content types are installed.
Static File Routes
The H5P router serves several static file paths required by the H5P editor and player:
| Route Pattern | Source Directory | Description |
|---|---|---|
/h5p/core/* | h5p/core/ | H5P core JavaScript and CSS files |
/h5p/editor/* | h5p/editor/ | H5P editor JavaScript and CSS files |
/h5p/libraries/* | h5p/libraries/ | Installed H5P library assets |
/h5p/content/<id>/* | h5p/content/<id>/ | Content-specific files (media, etc.) |
/h5p/temp/* | h5p/temp/ | Temporary files (uploads, editor temp). Searches all user subdirectories. |
/h5p/custom/* | h5p/custom/ | Custom CSS overrides (e.g. h5p-custom.css) |
These static routes are served by a combination of:
h5pAjaxExpressRouterfrom@lumieducation/h5p-express(core, editor, libraries, content, temp)- Custom
express.staticmiddleware (custom CSS) - Custom handler for temp files that searches across all user temp subdirectories
Embedding Note: Routes under /h5p/ have relaxed X-Frame-Options and CSP frame-ancestors headers to allow loading inside iframes (e.g. BigBlueButton integration). The server sets frame-ancestors * for any request path starting with /h5p/.
Health Check
GET /h5p/health
Auth: None
Response:
{
"success": true,
"service": "h5p",
"timestamp": "2025-03-20T14:30:00.000Z"
}
Content Ownership Model
Content ownership is tracked in the content_ownership PostgreSQL table:
| Column | Type | Description |
|---|---|---|
content_id | VARCHAR(100) (PK) | H5P content ID |
user_id | UUID (FK to creatiq_users) | Owner's user ID |
visibility | VARCHAR(20) | "private" or "public" (default: "private") |
published_at | TIMESTAMPTZ | Timestamp when content was first made public |
view_count | INTEGER | Number of times the play endpoint was called (default: 0) |
created_at | TIMESTAMPTZ | Ownership record creation time |
updated_at | TIMESTAMPTZ | Last update time (auto-updated via trigger) |
Key behaviors:
- Ownership is recorded automatically when content is created via
POST /h5p/content. - The owner is resolved from the JWT email claim via the
creatiq_userstable. - Only the owner can update, delete, or change visibility of content.
- Legacy content with no ownership record can be edited or deleted by any teacher; the first teacher to edit it becomes the owner.
- When content is deleted, the ownership record is also removed.
GET /h5p/content(list) returns only content owned by the requesting user.
Visibility States
| State | Description |
|---|---|
private | Default. Content is only visible to the owner. Listed only in the owner's content list. |
public | Content is discoverable in the marketplace and community. Other users can view and play it. |
Visibility is toggled via PATCH /h5p/content/:contentId/visibility. Only the content owner can change visibility.
File Upload Limits
| Limit | Value | Source |
|---|---|---|
H5P package import (POST /h5p/import) | 500 MB | multer config in h5p.ts |
Global file upload (express-fileupload) | 500 MB | server/index.ts fileUpload middleware |
Individual file size (h5pConfig.maxFileSize) | 100 MB | server/config/h5p.ts |
Total content size (h5pConfig.maxTotalSize) | 500 MB | server/config/h5p.ts |
| JSON body size | 10 MB | server/index.ts express.json config |
Media upload (POST /h5p/media/upload) | Limited by JSON body size (base64 in body) | Effective ~7.5 MB decoded |
Accepted MIME types for import:
application/zipapplication/x-zip-compressedapplication/octet-stream- Any file with
.h5pextension
Soft Delete Behavior
H5P content deletion is hard delete, not soft delete:
DELETE /h5p/content/:contentIdcallsh5pEditor.deleteContent()which removes the content directory from the filesystem (h5p/content/<contentId>/).- The
content_ownershipdatabase row is deleted viadeleteOwnership(contentId). - Any associated
content_curriculum_metadatarows remain orphaned (no cascade from content side). - The quality cache file (
quality.json) is removed along with the content directory. - There is no undo or trash/recycle bin mechanism. Deleted content cannot be recovered.
Standard Error Response Format
All endpoints return errors in a consistent format:
{
"success": false,
"error": "Human-readable error message"
}
Common HTTP status codes:
| Status | Meaning |
|---|---|
| 400 | Bad request (missing fields, invalid format) |
| 401 | Not authenticated or user not found |
| 403 | Not authorized (not the content owner) |
| 429 | Subscription quota exceeded |
| 500 | Internal server error |