Scheduled Video Thumbnails — Frontend Guide
Let a creator (Studio) or an admin change a video's thumbnail at a future time: upload the image
now, pick a date/time, and the backend swaps video.thumbnail automatically when that time arrives.
Multiple schedules: a video can have any number of pending schedules; each one fires independently at its own time. If two are pending, the one with the latest time ends up as the final thumbnail. Future only: the scheduled time must be in the future (UTC), otherwise the API returns 400. Cancellable: a schedule can be cancelled any time before it fires. Applied/cancelled rows stay in the list as history. Execution: handled by a Temporal workflow server-side — no polling needed from the frontend.
The big picture
1. Upload the image through the existing asset flow (presigned URL) → you get a publicUrl.
2. POST .../videos/{id}/thumbnail/schedule with { thumbnail: publicUrl, scheduledAt: <future UTC> }.
3. Show pending schedules via GET .../videos/{id}/thumbnail/scheduled (add ?status= for history).
4. Optionally cancel a pending one via POST .../thumbnail/scheduled/{scheduleId}/cancel.
5. At scheduledAt the backend sets video.thumbnail and marks the schedule "Applied".
The current thumbnail on the video is untouched until the schedule fires — scheduling is not the same as saving the thumbnail on the video edit form.
Endpoints
Same shape on both surfaces; only the prefix and auth differ.
| Action | Studio | Admin |
|---|---|---|
| Schedule a change | POST /studio/v1/videos/{id}/thumbnail/schedule |
POST /admin/v1/videos/{id}/thumbnail/schedule |
| List schedules (pending by default) | GET /studio/v1/videos/{id}/thumbnail/scheduled |
GET /admin/v1/videos/{id}/thumbnail/scheduled |
| Cancel a pending schedule | POST /studio/v1/videos/{id}/thumbnail/scheduled/{scheduleId}/cancel |
POST /admin/v1/videos/{id}/thumbnail/scheduled/{scheduleId}/cancel |
{id}is the encoded video id (the same one used byPUT .../videos/{id}).{scheduleId}is the encodedidfrom the schedule DTO (see below).- Auth: standard creator auth on Studio (the video must belong to the creator's channel — otherwise 404), standard admin auth on Admin.
1. Upload the thumbnail image (existing flow)
Nothing new here — same as uploading a thumbnail on the video edit page:
POST /studio/v1/assets/ { "fileName": "my-thumb.jpg" }
→ { "uploadUrl": "...", "publicUrl": "https://cdn.../api-assets/<guid>.jpg", ... }
PUT <uploadUrl> (raw file bytes)
Optionally run the file through POST /studio/v1/assets/optimize (multipart form, returns the
resized 1280×720 JPEG) before uploading. Keep the publicUrl — that's what you send as
thumbnail when scheduling.
2. Schedule a change
POST /studio/v1/videos/{id}/thumbnail/schedule
{
"thumbnail": "https://cdn.example.com/api-assets/3f2a9c1e-....jpg",
"scheduledAt": "2026-07-03T09:00:00Z"
}
scheduledAt must be an ISO-8601 UTC timestamp in the future. Convert from the user's local
timezone before sending (date.toISOString()).
200 response — the created schedule:
{
"id": "aB3xK9",
"videoId": "Qw8Zr2",
"thumbnail": "https://cdn.example.com/api-assets/3f2a9c1e-....jpg",
"status": 1,
"scheduledAt": "2026-07-03T09:00:00Z",
"createdAt": "2026-07-02T08:30:00Z",
"appliedAt": null,
"cancelledAt": null
}
3. List schedules
GET /studio/v1/videos/{id}/thumbnail/scheduled
200 response — array of the DTO above, newest scheduledAt first. By default it returns only
the pending (cancellable) schedules. Pass the optional status query parameter to fetch
another status instead — e.g. the applied history:
GET /studio/v1/videos/{id}/thumbnail/scheduled?status=2
status values
Enums are serialized as numbers:
| Value | Name | Meaning |
|---|---|---|
1 |
Scheduled | Pending — will fire at scheduledAt. Cancellable. |
2 |
Applied | Done — the video thumbnail was changed (appliedAt set). |
3 |
Cancelled | Cancelled before firing (cancelledAt set). |
4 |
Failed | Backend error (e.g. video deleted before firing). |
4. Cancel a pending schedule
POST /studio/v1/videos/{id}/thumbnail/scheduled/{scheduleId}/cancel
No body. 200 response: true.
Only status: 1 (Scheduled) entries can be cancelled — anything else returns 400. Hide/disable the
cancel button for other statuses.
Errors
Standard API error envelope:
- 404 — video (Studio: not yours) or schedule not found:
{ "errors": [{ "code": "NOT_FOUND", "message": "Video with id ... not found", "field": "video" }] } - 400 — validation or state errors, e.g.:
"Thumbnail is required."— empty/missingthumbnail"Scheduled time must be in the future."—scheduledAt≤ now (UTC)"Cannot cancel a thumbnail change in 'Applied' state. ..."— cancelling a non-pending entry
Shape: { "errors": [{ "code": "SERVER_ERROR", "message": "...", "field": null }] }
Suggested UI
On the video edit page (both Studio and Admin):
- Next to the thumbnail field, add "Schedule change" → opens a dialog with an image upload + date/time picker (min = now, disallow past).
- Under it, list pending schedules (thumbnail preview, local-time date, cancel button) from the GET endpoint; refresh the list after scheduling or cancelling.
- After
scheduledAtpasses, re-fetching the video shows the new thumbnail; the list entry flips tostatus: 2(Applied).
Timezone note: the backend works purely in UTC. Always display scheduledAt converted to the
user's local time, and label it (e.g. "Jul 3, 2026, 11:00 (your time)") to avoid confusion for
creators in different timezones.