Used by thousands of students worldwide - help us reach 150 members
Files & Videos
/filesLibrary of uploaded documents and videos stored in S3, with metadata (title, author, tags, categories, visibility) kept in MongoDB. Files and videos share the same underlying File model. Authentication failures are answered by the auth middleware with plain-text bodies (401 no token, 403 invalid token or missing role, 404 when the token's user no longer exists).
/files/uploadUpload a fileGET/files/download/:idDownload a fileGET/files/view/:idGet a temporary view URL for a fileGET/files/:id/metadataGet file metadataGET/filesList and search filesPOST/files/videos/uploadUpload a videoGET/files/videosList videosGET/files/videos/:idGet video details and playback URLUpload a file
Uploads a single file to S3 under a unique `uploads/<uuid>-<originalname>` key and saves its metadata. Any file type is accepted. `visibility` falls back to the model default `private` when omitted; any value other than `public`/`private` fails validation with a 500. The response contains the full stored document, including `s3Key` and `s3Bucket`.
Send the body as multipart/form-data.
Body parameters
filerequiredfile | The file to upload, under the field name `file`. Exactly one file, any MIME type, max 1 GB. |
titlestring | Display name, stored as `fileName`.Default: original file name |
authorstring | Author of the document.Default: 'Unknown' |
tagsstring | Comma-separated list of tags; each entry is trimmed.Default: [] |
categoriesstring | Comma-separated list of categories; each entry is trimmed.Default: ['uncategorized'] |
visibilitystring | `'public'` or `'private'`.Default: 'private' |
Errors
400No file uploaded. Only returned when `title` is supplied; without both a file and a `title` the handler fails earlier and answers 500.401No bearer token provided (plain-text body)403Invalid or expired token, or the user lacks the required role (plain-text body)500S3/database/validation error (e.g. invalid `visibility`). A file over 1 GB or an unexpected file field is answered with a plain-text 500 by the global error handler.
curl -X POST "https://app.ummahspot.com/files/upload" \
-H "Authorization: Bearer $SHARH_TOKEN" \
-F "file=@/path/to/file"{
"message": "File uploaded and metadata saved successfully",
"file": {
"_id": "66f2d1e2f3a4b5c6d7e8f901",
"fileName": "Ajrumiyyah Notes",
"s3Key": "uploads/3c1f9a52-7a0b-4d0e-9d55-0a4f1b2c3d4e-ajrumiyyah-notes.pdf",
"author": "Ibn Ajurrum",
"s3Bucket": "sharh-files",
"fileSize": 1843200,
"fileType": "application/pdf",
"tags": ["nahw", "grammar"],
"categories": ["arabic"],
"visibility": "public",
"thumbnailKey": null,
"uploadDate": "2026-09-18T09:00:00.000Z",
"createdDate": "2026-09-18T09:00:00.000Z",
"updatedDate": "2026-09-18T09:00:00.000Z",
"__v": 0
}
}Download a file
Streams the file contents from S3 through the API. Access depends on the file's `visibility`: public files are available to any signed-in user; private files only to admins, to the user who owns the OCR book created from that file, and to editors when the file is the source file of an OCR book. Anyone else gets the same 404 as for a missing file. No `Content-Disposition` header is set, so browsers render the file inline when they can.
Path parameters
idstring | File ObjectId |
Errors
401No bearer token provided (plain-text body)403Invalid or expired token, or the user lacks the required role (plain-text body)404File not found, or the file is private and the caller may not access it (`{"message":"File not found"}` in both cases)500Error retrieving the file (including a malformed `id`)
curl -X GET "https://app.ummahspot.com/files/download/:id" \
-H "Authorization: Bearer $SHARH_TOKEN"Binary stream of the file. Content-Type is the stored fileType (falls back to application/octet-stream).Get a temporary view URL for a file
Returns a presigned S3 URL for the file, valid for 5 minutes. Access depends on the file's `visibility`: public files are available to any signed-in user; private files only to admins, to the user who owns the OCR book created from that file, and to editors when the file is the source file of an OCR book. Anyone else gets the same 404 as for a missing file.
Path parameters
idstring | File ObjectId |
Errors
401No bearer token provided (plain-text body)403Invalid or expired token, or the user lacks the required role (plain-text body)404File not found, or the file is private and the caller may not access it (`{"message":"File not found"}` in both cases)500Error generating the view link (including a malformed `id`)
curl -X GET "https://app.ummahspot.com/files/view/:id" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"url": "https://sharh-files.s3.amazonaws.com/uploads/3c1f9a52-...-ajrumiyyah-notes.pdf?X-Amz-Expires=300&X-Amz-Signature=..."
}Get file metadata
Returns a subset of the stored metadata for one file. Access depends on the file's `visibility`: public files are available to any signed-in user; private files only to admins, to the user who owns the OCR book created from that file, and to editors when the file is the source file of an OCR book. Anyone else gets the same 404 as for a missing file.
Path parameters
idstring | File ObjectId |
Errors
401No bearer token provided (plain-text body)403Invalid or expired token, or the user lacks the required role (plain-text body)404File not found, or the file is private and the caller may not access it (`{"message":"File not found"}` in both cases)500Database error (including a malformed `id`)
curl -X GET "https://app.ummahspot.com/files/:id/metadata" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"fileName": "Ajrumiyyah Notes",
"fileType": "application/pdf",
"author": "Ibn Ajurrum",
"uploadDate": "2026-09-18T09:00:00.000Z",
"tags": ["nahw", "grammar"],
"categories": ["arabic"]
}List and search files
Lists file records (uploaded videos are stored in the same collection and are included). No authentication is required and results are limited to `visibility: public`; the handler reads an optional bearer token itself and only an admin token lifts that restriction. There is no pagination — every match is returned. `s3Key` and `s3Bucket` are never included. With `searchTerm`, a MongoDB full-text search over fileName, author, tags and categories is used, results are sorted by relevance and each item carries a `score`; otherwise no sort order is applied. All filters are combined with AND.
Query parameters
fileNamestring | Case-insensitive regex match on the file name. |
authorstring | Case-insensitive regex match on the author. |
uploadDatestring | Exact match on the upload timestamp (parsed with `new Date()`). |
fileSizenumber | Exact file size in bytes. |
fileTypestring | Case-insensitive regex match on the MIME type, e.g. `pdf`. |
tagsstring | Comma-separated tags; matches files having at least one of them (case-insensitive, partial match). |
categoriesstring | Comma-separated categories; matches files having at least one of them (case-insensitive, partial match). |
searchTermstring | Full-text search term; enables relevance sorting and adds `score` to each result. |
Errors
500Database error (e.g. an invalid regex in a filter)
curl -X GET "https://app.ummahspot.com/files"{
"files": [
{
"_id": "66f2d1e2f3a4b5c6d7e8f901",
"fileName": "Ajrumiyyah Notes",
"author": "Ibn Ajurrum",
"fileSize": 1843200,
"fileType": "application/pdf",
"tags": ["nahw", "grammar"],
"categories": ["arabic"],
"visibility": "public",
"thumbnailKey": null,
"uploadDate": "2026-09-18T09:00:00.000Z",
"createdDate": "2026-09-18T09:00:00.000Z",
"updatedDate": "2026-09-18T09:00:00.000Z",
"__v": 0
}
]
}Upload a video
Uploads a single video to S3 under `videos/<uuid>-<originalname>`, generates a 320x180 JPEG thumbnail from the 2-second mark with ffmpeg, uploads it under `thumbnails/`, and saves the metadata. If thumbnail generation fails the upload still succeeds, but the record keeps the intended `thumbnailKey` and the returned `thumbnailUrl` will point at a missing object. `thumbnailUrl` is a presigned URL valid for 1 hour.
Send the body as multipart/form-data.
Body parameters
videorequiredfile | The video, under the field name `video`. Exactly one file, max 1 GB. Allowed MIME types: video/mp4, video/quicktime, video/x-msvideo, video/x-ms-wmv, video/webm. |
fileNamestring | Display name of the video.Default: original file name |
descriptionstring | Video description.Default: '' |
authorstring | Author / presenter.Default: 'Unknown' |
tagsstring | Comma-separated list of tags; each entry is trimmed.Default: [] |
categoriesstring | Comma-separated list of categories; each entry is trimmed.Default: [] |
visibilitystring | `'public'` or `'private'`.Default: 'private' |
Errors
400No video uploaded401No bearer token provided (plain-text body)403Invalid or expired token, or the user lacks the required role (plain-text body)500S3/database/validation error (e.g. invalid `visibility`). A disallowed MIME type or a file over 1 GB is answered with a plain-text 500 by the global error handler.
curl -X POST "https://app.ummahspot.com/files/videos/upload" \
-H "Authorization: Bearer $SHARH_TOKEN" \
-F "video=@/path/to/file"{
"message": "Video uploaded successfully",
"video": {
"id": "66f2e4a5b6c7d8e9f0a1b2c3",
"fileName": "Getting started with the reader",
"author": "Sharh Team",
"description": "A five minute tour of the book reader.",
"thumbnailUrl": "https://sharh-files.s3.amazonaws.com/thumbnails/9e8d7c6b-...-tour.jpg?X-Amz-Signature=..."
}
}List videos
Paginated list of records from the shared file collection, newest upload first. Note that the handler does not filter by MIME type, so non-video files uploaded through `/files/upload` also appear. No authentication is required and results are limited to `visibility: public`; the handler reads an optional bearer token itself and only an admin token lifts that restriction. `s3Key` and `s3Bucket` are never included. Records with a `thumbnailKey` get a 1-hour presigned `thumbnailUrl` (null if URL generation fails). Page-based pagination via `page` and `limit`.
Query parameters
fileNamestring | Case-insensitive regex match on the name. |
authorstring | Case-insensitive regex match on the author. |
tagsstring | Comma-separated tags; matches records having at least one of them (case-insensitive, partial match). |
categoriesstring | Comma-separated categories; matches records having at least one of them (case-insensitive, partial match). |
pagenumber | 1-based page number.Default: 1 |
limitnumber | Items per page.Default: 10 |
Errors
500Database error (e.g. an invalid regex in a filter)
curl -X GET "https://app.ummahspot.com/files/videos"{
"videos": [
{
"_id": "66f2e4a5b6c7d8e9f0a1b2c3",
"fileName": "Getting started with the reader",
"author": "Sharh Team",
"fileSize": 52428800,
"fileType": "video/mp4",
"description": "A five minute tour of the book reader.",
"tags": ["tutorial"],
"categories": ["getting-started"],
"visibility": "public",
"thumbnailKey": "thumbnails/9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b-tour.jpg",
"uploadDate": "2026-09-18T09:30:00.000Z",
"createdDate": "2026-09-18T09:30:00.000Z",
"updatedDate": "2026-09-18T09:30:00.000Z",
"__v": 0,
"thumbnailUrl": "https://sharh-files.s3.amazonaws.com/thumbnails/9e8d7c6b-...-tour.jpg?X-Amz-Signature=..."
}
],
"currentPage": 1,
"totalPages": 1,
"totalVideos": 1
}Get video details and playback URL
Returns the video's metadata with a presigned playback URL (`viewUrl`) and thumbnail URL, both valid for 1 hour. Unlike the list endpoint, this route requires a logged-in user. Visibility is enforced: private videos are only returned to admins; everyone else gets a 403. `thumbnailUrl` is null when the record has no thumbnail.
Path parameters
idstring | Video (File) ObjectId |
Errors
401No bearer token provided (plain-text body)403Invalid or expired token, or the user lacks the required role (plain-text body)403The video is private and the current user is not an admin (JSON body)404Video not found500Database/S3 error (including a malformed `id`)
curl -X GET "https://app.ummahspot.com/files/videos/:id" \
-H "Authorization: Bearer $SHARH_TOKEN"{
"id": "66f2e4a5b6c7d8e9f0a1b2c3",
"fileName": "Getting started with the reader",
"description": "A five minute tour of the book reader.",
"author": "Sharh Team",
"uploadDate": "2026-09-18T09:30:00.000Z",
"createdDate": "2026-09-18T09:30:00.000Z",
"updatedDate": "2026-09-18T09:30:00.000Z",
"fileType": "video/mp4",
"fileSize": 52428800,
"tags": ["tutorial"],
"categories": ["getting-started"],
"visibility": "public",
"viewUrl": "https://sharh-files.s3.amazonaws.com/videos/1a2b3c4d-...-tour.mp4?X-Amz-Signature=...",
"thumbnailUrl": "https://sharh-files.s3.amazonaws.com/thumbnails/9e8d7c6b-...-tour.jpg?X-Amz-Signature=..."
}