Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/barcode8/VideoHub/llms.txt

Use this file to discover all available pages before exploring further.

VideoHub uses a two-step upload process to handle large video files without running into HTTP timeout issues. The first request reserves a MongoDB ObjectId without touching the database. The second request immediately creates a database record marked as hidden, then fires off an asynchronous Cloudinary upload in the background — so your client gets a fast response while the heavy processing continues server-side. Once Cloudinary finishes, the record is automatically flipped to published.

Two-Step Upload Flow

1

Initialize — reserve a video ID

Call POST /api/v1/videos/init to generate a fresh MongoDB ObjectId. No database record is created at this point; the ID is purely in-memory. Hold on to the returned videoId — you will use it in the next step.
2

Publish — upload the file and metadata

Call POST /api/v1/videos/:videoId with the videoId from the previous step. The server creates a database record immediately with isPublished: false and responds right away. A background process then uploads the video (and optional thumbnail) to Cloudinary. Once processing is complete the record’s isPublished field is set to true and the real Cloudinary URLs replace the temporary "processing" placeholders.

POST /api/v1/videos/init

Generates a MongoDB ObjectId to be used as the video’s _id in the subsequent publish call. No database write occurs. Authentication: Required (verifyJwt)

Request

No request body is needed.

Response

{
  "statusCode": 200,
  "data": {
    "videoId": "6657f3c2a4e1b2c3d4e5f601"
  },
  "message": "Video Id generated successfully"
}

Example

curl -X POST https://api.videohub.example/api/v1/videos/init \
  -H "Authorization: Bearer <your_jwt_token>"

POST /api/v1/videos/:videoId

Creates a video document with isPublished: false and immediately returns. Cloudinary upload and processing run asynchronously in the background. Once complete, isPublished is set to true and the real URLs are stored. Authentication: Required (verifyJwt)
Videos longer than 1 hour (3600 seconds) are rejected during background processing. The Cloudinary asset is destroyed and the database record is deleted automatically. There is no error surfaced to the original HTTP response since processing is async — monitor your server logs if you suspect a video was rejected.
If no thumbnail file is provided, VideoHub auto-generates one from the video using Cloudinary’s so_auto transformation. The thumbnail URL is constructed by splitting the Cloudinary video URL on /upload/, inserting so_auto/ after it, and replacing the .mp4 extension with .jpg.

Path Parameters

videoId
string
required
The MongoDB ObjectId returned by POST /api/v1/videos/init. Using a valid ObjectId that was not generated by the init endpoint is allowed, but doing so risks ID collisions.

Request Body (multipart/form-data)

videoFile
file
required
The video file to upload. Passed as a binary file field in the multipart form. Supported formats depend on your Cloudinary plan.
title
string
required
The display title of the video. The controller explicitly validates that this field is present and non-empty.
description
string
required
A description of the video’s content. The controller does not explicitly validate this field, but the Mongoose schema marks it required: true — omitting it will cause the database write to fail. Always provide a non-empty value.
thumbnail
file
An optional image file to use as the video thumbnail. If omitted, a thumbnail is auto-generated from the video using Cloudinary’s so_auto transformation.

Response

Returns the video document as it exists immediately after the database insert — before background processing completes. The videoFile and thumbnail fields will read "processing" at this stage.
{
  "statusCode": 200,
  "data": {
    "_id": "6657f3c2a4e1b2c3d4e5f601",
    "title": "My First Vlog",
    "description": "A quick trip through the city.",
    "videoFile": "processing",
    "thumbnail": "processing",
    "duration": 0,
    "views": 0,
    "isPublished": false,
    "owner": "6657f3c2a4e1b2c3d4e5f600",
    "createdAt": "2025-01-15T10:30:00.000Z",
    "updatedAt": "2025-01-15T10:30:00.000Z"
  },
  "message": "Video is uploading and processing in the background"
}

Example — Full Two-Step Flow

# Step 1: Get a video ID
VIDEO_ID=$(curl -s -X POST https://api.videohub.example/api/v1/videos/init \
  -H "Authorization: Bearer <your_jwt_token>" \
  | jq -r '.data.videoId')

# Step 2: Publish the video using the reserved ID
curl -X POST "https://api.videohub.example/api/v1/videos/${VIDEO_ID}" \
  -H "Authorization: Bearer <your_jwt_token>" \
  -F "videoFile=@/path/to/video.mp4" \
  -F "thumbnail=@/path/to/cover.jpg" \
  -F "title=My First Vlog" \
  -F "description=A quick trip through the city."

GET /api/v1/videos

Returns a paginated feed of published videos. Supports full-text search, sorting, and filtering by channel owner. When a userId filter is applied and the authenticated requester is that user, unpublished videos are also included — useful for a creator’s own dashboard. Authentication: Optional (verifyJWTIfAvailable)
Omit userId to get the general public feed, which always shows only published videos regardless of authentication state.

Query Parameters

page
number
default:"1"
The page number for pagination. Must be a positive integer.
limit
number
default:"10"
The number of videos to return per page. Must be a positive integer.
query
string
A search string matched case-insensitively against both the title and description fields using a regex filter.
sortBy
string
The document field to sort results by — for example createdAt or views. When omitted, results default to createdAt descending (newest first).
sortType
string
Sort direction. Accepts "asc" (ascending) or "desc" (descending). Any value other than "asc" is treated as descending.
userId
string
A MongoDB ObjectId belonging to a channel owner. When provided, only that user’s videos are returned. If the authenticated requester matches this userId, their unpublished videos are included as well. Returns a 400 if the value is not a valid ObjectId.

Response

Returns a paginated result object produced by mongoose-aggregate-paginate-v2. Each video document includes an embedded ownerDetails object containing the owner’s username and avatar (resolved via $lookup on the users collection).
{
  "statusCode": 200,
  "data": {
    "docs": [
      {
        "_id": "6657f3c2a4e1b2c3d4e5f601",
        "title": "My First Vlog",
        "description": "A quick trip through the city.",
        "videoFile": "https://res.cloudinary.com/demo/video/upload/v1/myvideo.mp4",
        "thumbnail": "https://res.cloudinary.com/demo/image/upload/v1/mythumb.jpg",
        "duration": 312.5,
        "views": 1024,
        "isPublished": true,
        "owner": "6657f3c2a4e1b2c3d4e5f600",
        "ownerDetails": {
          "_id": "6657f3c2a4e1b2c3d4e5f600",
          "username": "alexvlogs",
          "avatar": "https://res.cloudinary.com/demo/image/upload/v1/avatar.jpg"
        },
        "createdAt": "2025-01-15T10:30:00.000Z",
        "updatedAt": "2025-01-15T10:32:45.000Z"
      }
    ],
    "totalDocs": 58,
    "limit": 10,
    "page": 1,
    "totalPages": 6,
    "hasNextPage": true,
    "hasPrevPage": false
  },
  "message": "Videos fetched successfully"
}

Example

# Search for "city" videos, sorted by most views, page 2
curl -G "https://api.videohub.example/api/v1/videos" \
  -H "Authorization: Bearer <your_jwt_token>" \
  --data-urlencode "query=city" \
  --data-urlencode "sortBy=views" \
  --data-urlencode "sortType=desc" \
  --data-urlencode "page=2" \
  --data-urlencode "limit=10"

Build docs developers (and LLMs) love