# 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: }. 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 by `PUT .../videos/{id}`). - `{scheduleId}` is the encoded `id` from 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/.jpg", ... } PUT (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 ``` ```json { "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: ```json { "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/missing `thumbnail` - `"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): 1. Next to the thumbnail field, add **"Schedule change"** → opens a dialog with an image upload + date/time picker (min = now, disallow past). 2. Under it, list pending schedules (thumbnail preview, local-time date, cancel button) from the GET endpoint; refresh the list after scheduling or cancelling. 3. After `scheduledAt` passes, re-fetching the video shows the new thumbnail; the list entry flips to `status: 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.