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:
recordingDateis not returned by YouTube during import and is alwaysnullafter a channel sync. Setting it locally will correctly mark the video as push-pending.- Providing
collaboratorIdsupdatesVideo.collaboratorIds(a JSON string array on theVideorow). This is the single field the render engine reads to resolve{collab.*}tokens. - Providing
templateIdapplies the template's block configuration to the video'sVideoConfig.
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
lastSyncedHashbaseline.
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:
- Copies
pendingRemoteSnapshotfields into the live Video columns (title,tags,categoryId,privacyStatus,defaultLanguage,defaultAudioLanguage,selfDeclaredMadeForKids,embeddable,license,recordingDate) - Sets
youtubeDescription = renderedDescription = pendingRemoteDescription - Promotes
pendingRemoteSnapshottoyoutubeSnapshot - Recomputes
lastSyncedHashfrom the accepted state, setslastSyncedAt = now - Clears
pendingRemoteSnapshot,pendingRemoteDescription,remoteConflict - Writes an audit log entry with
action: 'accept-remote'
Errors:
400if there is nopendingRemoteSnapshotto accept404if 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[]