Files

4.9 KiB

Videos API

Base path: /api/v1/videos

All endpoints require Authorization: Bearer <jwt>.

Related: 01 - Video Management, 04 - Database Schema, 11 - Bulk Jobs API, 07 - Playlists API


GET /videos

List videos for the current team with pagination, sorting, and filtering.

Query parameters:

Param Type Description
page number Page number (default: 1)
pageSize number Items per page (default: 20)
sortBy string Field to sort by
sortOrder asc|desc Sort direction
search string Title search (case-insensitive)
lintStatus OK|WARNING|ERROR Exact lint status match
hasLintIssues boolean Match ERROR or WARNING
privacyStatus PUBLIC|PRIVATE|UNLISTED Privacy filter
scheduled boolean Videos with a future scheduledAt
notScheduled boolean Videos without a scheduledAt value
remoteConflict boolean Videos with a remote conflict flag
pendingSync boolean Videos where hasPendingChanges is true
deletedOnYouTube boolean Videos with youtubeDeletedAt set

Response:

{
  "data": [ "VideoSummary[]" ],
  "total": 142,
  "page": 1,
  "pageSize": 20
}

hasPendingChanges is computed per video by comparing the current state hash against lastSyncedHash. See 01 - System Overview for details on the sync status logic.


GET /videos/:id

Get full video detail including lint results, playlists, and collaborators.

Response: VideoDetail object


PATCH /videos/:id

Update video metadata fields.

Request body (all fields optional):

{
  "title": "string",
  "tags": ["string"],
  "privacyStatus": "PUBLIC|PRIVATE|UNLISTED",
  "scheduledAt": "ISO datetime",
  "categoryId": "string",
  "selfDeclaredMadeForKids": false,
  "embeddable": true,
  "license": "youtube|creativeCommon",
  "defaultLanguage": "en",
  "defaultAudioLanguage": "en",
  "recordingDate": "YYYY-MM-DD",
  "gameTitle": "string",
  "collaboratorIds": ["cuid"],
  "templateId": "cuid"
}

Notes:

  • recordingDate is not returned by YouTube during import and is always null after a channel sync. Setting it locally will correctly mark the video as push-pending.
  • Providing collaboratorIds updates Video.collaboratorIds (a JSON string array on the Video row). This is the single field the render engine reads to resolve {collab.*} tokens.
  • Providing templateId applies the template's block configuration to the video's VideoConfig.

POST /videos/:id/sync

Enqueue a YouTube sync job for the video. Returns immediately; processing is asynchronous via BullMQ.

The processor renders the description, computes a metadata hash, skips if unchanged, then pushes all editable fields to YouTube. After a successful push, renderedDescription and lastSyncedHash are updated on the video row.

Response:

{ "queued": true }

Cost: 50 YouTube API quota units per successful push. See 10 - Quota API.


POST /videos/:id/refresh

Pull latest metadata from YouTube and update the local record. This is a synchronous operation.

Re-importing after making local changes will reset the lastSyncedHash baseline.

Response: Updated VideoDetail


POST /videos/:id/accept-remote

Resolve a detected remote conflict by adopting the pending remote snapshot as the new local state. Requires EDITOR role. Zero YouTube API calls — operates entirely on the snapshot captured by detectConflict() when the conflict was first detected.

Behavior:

  1. Copies pendingRemoteSnapshot fields into the live Video columns (title, tags, categoryId, privacyStatus, defaultLanguage, defaultAudioLanguage, selfDeclaredMadeForKids, embeddable, license, recordingDate)
  2. Sets youtubeDescription = renderedDescription = pendingRemoteDescription
  3. Promotes pendingRemoteSnapshot to youtubeSnapshot
  4. Recomputes lastSyncedHash from the accepted state, sets lastSyncedAt = now
  5. Clears pendingRemoteSnapshot, pendingRemoteDescription, remoteConflict
  6. Writes an audit log entry with action: 'accept-remote'

Errors:

  • 400 if there is no pendingRemoteSnapshot to accept
  • 404 if the video is not in the caller's team

Response: Updated Video row.

For the opposite direction ("keep local, overwrite remote"), just call POST /videos/:id/sync — the sync processor overwrites remote and clears remoteConflict on success.


POST /videos/:id/render

Render the video's description using the current VideoConfig and return the result without saving or pushing.

Response:

{ "rendered": "string" }

DELETE /videos/:id

Soft-delete the video (marks as deleted locally). Does not delete the video from YouTube.


GET /youtube-sync/channels

List connected YouTube channels for the current team.

Base path: /api/v1/youtube-sync/channels

Response: Channel[]