Collections API Reference
Base path: /api/collections
Collections organize H5P content items into ordered groups with optional curriculum metadata. Each collection supports Bloom's Taxonomy gap analysis to suggest content that fills cognitive-level gaps.
Endpoints
POST /api/collections
Create a new collection.
Auth: Teacher (JWT, teacherAuthMiddleware)
Request Body:
{
"title": "Algebra Fundamentals",
"description": "Core algebra exercises for Grade 8",
"frameworkCode": "TR-MEB-2024",
"frameworkName": "MEB 2024",
"gradeCode": "grade-8",
"gradeName": "8th Grade",
"subjectCode": "math",
"subjectName": "Mathematics",
"topicCodes": ["algebra-basics", "equations"],
"topicNames": ["Algebra Basics", "Equations"],
"difficulty": "medium",
"language": "tr"
}
| Field | Type | Required | Constraints |
|---|---|---|---|
title | string | Yes | 1-500 chars |
description | string | No | max 2000 chars |
frameworkCode | string | No | max 50 chars |
frameworkName | string | No | max 255 chars |
gradeCode | string | No | max 50 chars |
gradeName | string | No | max 255 chars |
subjectCode | string | No | max 100 chars |
subjectName | string | No | max 255 chars |
topicCodes | string[] | No | each max 100 chars |
topicNames | string[] | No | each max 255 chars |
difficulty | enum | No | easy, medium, hard |
language | string | No | max 10 chars |
Response (201):
{
"success": true,
"data": {
"id": "uuid",
"userId": "uuid",
"title": "Algebra Fundamentals",
"description": "Core algebra exercises for Grade 8",
"coverImageUrl": null,
"visibility": "private",
"frameworkCode": "TR-MEB-2024",
"frameworkName": "MEB 2024",
"gradeCode": "grade-8",
"gradeName": "8th Grade",
"subjectCode": "math",
"subjectName": "Mathematics",
"topicCodes": ["algebra-basics", "equations"],
"topicNames": ["Algebra Basics", "Equations"],
"difficulty": "medium",
"language": "tr",
"itemCount": 0,
"createdAt": "2025-01-15T10:00:00.000Z",
"updatedAt": "2025-01-15T10:00:00.000Z"
}
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Validation error: ... | Invalid request body |
| 401 | User not found | JWT valid but user not in DB |
| 500 | Failed to create collection | Internal error |
GET /api/collections
List the authenticated user's collections with pagination.
Auth: Authenticated user (JWT, authMiddleware)
Query Parameters:
| Param | Type | Default | Constraints |
|---|---|---|---|
offset | integer | 0 | min 0 |
limit | integer | 20 | 1-50 |
Response (200):
{
"success": true,
"data": [
{
"id": "uuid",
"userId": "uuid",
"title": "Algebra Fundamentals",
"description": "Core algebra exercises for Grade 8",
"coverImageUrl": null,
"visibility": "private",
"frameworkCode": "TR-MEB-2024",
"frameworkName": "MEB 2024",
"gradeCode": "grade-8",
"gradeName": "8th Grade",
"subjectCode": "math",
"subjectName": "Mathematics",
"topicCodes": ["algebra-basics"],
"topicNames": ["Algebra Basics"],
"difficulty": "medium",
"language": "tr",
"itemCount": 5,
"createdAt": "2025-01-15T10:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}
],
"total": 12
}
Collections are ordered by updatedAt DESC. The server-side limit cap is 100.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Validation error: ... | Invalid query parameters |
| 401 | User not found | JWT valid but user not in DB |
| 500 | Failed to list collections | Internal error |
GET /api/collections/:id
Get collection detail with enriched items. Items include H5P metadata, curriculum data, quality scores, ownership, and access status.
Auth: Authenticated user (JWT, authMiddleware)
Access rules: Owner can always view. Non-owners can view only if collection visibility is public.
Response (200):
{
"success": true,
"data": {
"collection": {
"id": "uuid",
"userId": "uuid",
"title": "Algebra Fundamentals",
"visibility": "private",
"itemCount": 2,
"...": "..."
},
"items": [
{
"id": "uuid",
"collectionId": "uuid",
"contentId": "42",
"position": 0,
"addedAt": "2025-01-15T10:30:00.000Z",
"title": "Quadratic Equations Quiz",
"mainLibrary": "H5P.QuestionSet",
"contentTypeSlug": "question-set",
"authorId": "uuid",
"authorName": "Teacher A",
"status": "available",
"curriculum": {
"frameworkCode": "TR-MEB-2024",
"frameworkName": "MEB 2024",
"gradeCode": "grade-8",
"gradeName": "8th Grade",
"subjectCode": "math",
"subjectName": "Mathematics",
"topicCodes": ["equations"],
"topicNames": ["Equations"],
"difficulty": "medium",
"language": "tr"
},
"quality": {
"overallScore": 85,
"dimensions": {}
}
}
]
}
}
Item status values:
| Status | Meaning |
|---|---|
available | Content exists and is accessible to the requesting user |
unavailable | Content has been deleted or cannot be read from H5P storage |
restricted | Content is private and belongs to another user |
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | User not found | JWT valid but user not in DB |
| 404 | Collection not found | ID does not exist or access denied |
| 500 | Failed to get collection detail | Internal error |
PATCH /api/collections/:id
Update collection metadata. Only the owner can update.
Auth: Teacher (JWT, teacherAuthMiddleware)
Request Body: Any subset of the fields below.
{
"title": "Updated Title",
"description": "New description",
"visibility": "public",
"frameworkCode": "TR-MEB-2024",
"frameworkName": "MEB 2024",
"gradeCode": "grade-8",
"gradeName": "8th Grade",
"subjectCode": "math",
"subjectName": "Mathematics",
"topicCodes": ["algebra-basics"],
"topicNames": ["Algebra Basics"],
"difficulty": "hard",
"language": "en"
}
| Field | Type | Constraints |
|---|---|---|
title | string | 1-500 chars |
description | string | max 2000 chars |
visibility | enum | private, public |
frameworkCode | string | max 50 chars |
frameworkName | string | max 255 chars |
gradeCode | string | max 50 chars |
gradeName | string | max 255 chars |
subjectCode | string | max 100 chars |
subjectName | string | max 255 chars |
topicCodes | string[] | each max 100 chars |
topicNames | string[] | each max 255 chars |
difficulty | enum | easy, medium, hard |
language | string | max 10 chars |
Response (200):
{
"success": true,
"message": "Collection updated"
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Validation error: ... | Invalid request body |
| 401 | User not found | JWT valid but user not in DB |
| 404 | User does not own collection | Not the collection owner |
| 500 | Failed to update collection | Internal error |
DELETE /api/collections/:id
Delete a collection and all its items. Only the owner can delete.
Auth: Teacher (JWT, teacherAuthMiddleware)
Response (200):
{
"success": true,
"message": "Collection deleted"
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | User not found | JWT valid but user not in DB |
| 404 | User does not own collection / Collection not found | Not owner or does not exist |
| 500 | Failed to delete collection | Internal error |
POST /api/collections/:id/items
Add one or more content items to a collection. Content existence and accessibility are verified via H5P before adding. Items are appended at the end (next available position).
Auth: Teacher (JWT, teacherAuthMiddleware)
Request Body (single item):
{
"contentId": "42"
}
Request Body (batch):
{
"contentIds": ["42", "43", "44"]
}
One of contentId or contentIds must be provided.
| Field | Type | Constraints |
|---|---|---|
contentId | string | max 100 chars |
contentIds | string[] | each max 100 chars |
Response (201, single):
{
"success": true,
"data": {
"id": "uuid",
"collectionId": "uuid",
"contentId": "42",
"position": 5,
"addedAt": "2025-01-15T11:00:00.000Z"
}
}
Response (201, batch):
{
"success": true,
"data": [
{
"id": "uuid",
"collectionId": "uuid",
"contentId": "42",
"position": 5,
"addedAt": "2025-01-15T11:00:00.000Z"
},
{
"id": "uuid",
"collectionId": "uuid",
"contentId": "43",
"position": 6,
"addedAt": "2025-01-15T11:00:00.000Z"
}
]
}
Batch add uses ON CONFLICT DO NOTHING for duplicate content IDs within the same collection. The item_count on the collection is incremented only for actually inserted items.
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Either contentId or contentIds must be provided | Missing both fields |
| 401 | User not found | JWT valid but user not in DB |
| 404 | Content {id} not found or not accessible | H5P content does not exist |
| 404 | User does not own collection | Not the collection owner |
| 409 | Item already exists | Duplicate content in collection (single add) |
| 500 | Failed to add item to collection | Internal error |
DELETE /api/collections/:id/items/:itemId
Remove a single item from a collection. The itemId is the collection_item UUID, not the H5P content ID.
Auth: Teacher (JWT, teacherAuthMiddleware)
Response (200):
{
"success": true,
"message": "Item removed from collection"
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | User not found | JWT valid but user not in DB |
| 404 | Item not found in collection | Item ID does not exist in this collection |
| 404 | User does not own collection | Not the collection owner |
| 500 | Failed to remove item from collection | Internal error |
PATCH /api/collections/:id/items/reorder
Reorder items within a collection by specifying new positions.
Auth: Teacher (JWT, teacherAuthMiddleware)
Request Body:
{
"items": [
{ "id": "item-uuid-1", "position": 0 },
{ "id": "item-uuid-2", "position": 1 },
{ "id": "item-uuid-3", "position": 2 }
]
}
| Field | Type | Constraints |
|---|---|---|
items | array | min 1 element |
items[].id | string | UUID format |
items[].position | integer | min 0 |
Response (200):
{
"success": true,
"message": "Items reordered"
}
Errors:
| Status | Error | Cause |
|---|---|---|
| 400 | Validation error: ... | Invalid request body |
| 401 | User not found | JWT valid but user not in DB |
| 404 | User does not own collection | Not the collection owner |
| 500 | Failed to reorder items | Internal error |
GET /api/collections/:id/suggestions
Get smart content suggestions for a collection based on curriculum metadata and Bloom's Taxonomy gap analysis. Returns public content that matches the collection's curriculum filters, sorted so that content filling Bloom's gaps appears first.
Auth: Authenticated user (JWT, authMiddleware)
How it works:
- Reads the collection's existing items and resolves each item's H5P content type to a Bloom's level using the
CONTENT_TYPE_BLOOMS_MAP. - Computes a Bloom's distribution (percentage per level) and identifies gaps (levels with 0% coverage).
- Queries public content matching the collection's curriculum filters (
frameworkCode,gradeCode,subjectCode,difficulty,language), excluding content already in the collection and content owned by the requesting user. - Sorts suggestions so that content filling Bloom's gaps ranks first.
Bloom's ideal distribution (target):
| Level | Target % |
|---|---|
| Remember | 17.5 |
| Understand | 17.5 |
| Apply | 30.0 |
| Analyze | 11.67 |
| Evaluate | 11.67 |
| Create | 11.66 |
Response (200):
{
"success": true,
"data": {
"matchingContents": [
{
"contentId": "55",
"title": "Interactive Sorting Exercise",
"mainLibrary": "H5P.DragQuestion",
"contentTypeSlug": "drag-question",
"bloomsLevel": "apply",
"curriculum": {
"frameworkName": "MEB 2024",
"stageName": "Middle School",
"gradeName": "8th Grade",
"subjectName": "Mathematics",
"topicNames": ["Equations"],
"difficulty": "medium",
"language": "tr"
}
}
],
"bloomsAnalysis": {
"distribution": {
"remember": 25,
"understand": 25,
"apply": 50,
"analyze": 0,
"evaluate": 0,
"create": 0
},
"gaps": ["analyze", "evaluate", "create"],
"score": 50
}
}
}
bloomsAnalysis fields:
| Field | Type | Description |
|---|---|---|
distribution | object | Percentage of items at each Bloom's level (summing to ~100) |
gaps | string[] | Bloom's levels with 0% representation |
score | integer | Percentage of Bloom's levels that have at least one item (0-100) |
Errors:
| Status | Error | Cause |
|---|---|---|
| 401 | User not found | JWT valid but user not in DB |
| 404 | Collection not found | ID does not exist or access denied |
| 500 | Failed to get suggestions | Internal error |
Data Model
Collection
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated primary key |
userId | UUID | Owner user ID |
title | string | Collection title |
description | string | Optional description |
coverImageUrl | string | Optional cover image URL |
visibility | enum | private (default) or public |
frameworkCode | string | Curriculum framework code |
frameworkName | string | Curriculum framework display name |
gradeCode | string | Grade level code |
gradeName | string | Grade level display name |
subjectCode | string | Subject code |
subjectName | string | Subject display name |
topicCodes | string[] | Topic codes |
topicNames | string[] | Topic display names |
difficulty | string | Content difficulty level |
language | string | Language code |
itemCount | integer | Number of items in collection |
createdAt | ISO 8601 | Creation timestamp |
updatedAt | ISO 8601 | Last update timestamp |
CollectionItem
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated primary key |
collectionId | UUID | Parent collection ID |
contentId | string | H5P content ID |
position | integer | Sort order (0-based) |
addedAt | ISO 8601 | Timestamp when added |
Unique constraint: (collectionId, contentId) -- a content item can appear at most once in a collection.