Upload an episode video
Video uploads never touch our servers. The admin requests a CF Stream direct upload URL from the API; your browser uploads directly to Cloudflare using the TUS resumable protocol; CF calls back over HMAC-verified webhook when the asset is ready.
The flow
Section titled “The flow”sequenceDiagram autonumber participant U as Admin browser participant A as TwistCue API participant CF as Cloudflare Stream
U->>A: POST /admin/media/uploads/direct<br/>{kind, seriesId?, episodeId?, sizeBytes, meta} A->>CF: Create direct upload (TUS) CF-->>A: {uploadUrl, uid} A-->>U: {uploadUrl, assetId, provider_id=uid}<br/>media_asset row (status=uploading) U->>CF: TUS upload (resumable) CF-->>U: 200 (bytes accepted) CF->>A: POST /admin/media/webhook/cf-stream<br/>(HMAC-signed) A->>A: mark asset ready<br/>store playbackId, duration, thumbs Note over U: Poll GET /admin/media/assets/:id<br/>or subscribe via query invalidationStep-by-step in the UI
Section titled “Step-by-step in the UI”- Open the series → Episodes tab → New episode.
- Enter episode number, title, and (optionally) synopsis.
- On the Media section, click Upload video.
- Pick your file. The browser starts a TUS resumable upload; you get a live progress bar. You can close the tab: the upload resumes when you re-open it (TUS handles the offset).
- The row appears in Media → Uploads with status
uploading, thenprocessingwhen CF starts transcoding. - When status flips to
ready, the episode row auto-links to the new asset and shows the auto-extracted thumbnail candidates. Pick one, or upload a custom.
Asset states
Section titled “Asset states”| State | Meaning | Recoverable? |
|---|---|---|
uploading | Bytes still landing at CF. | Yes: resume the TUS upload. |
processing | CF transcoding. Nothing to do; wait. | N/A |
ready | Playable. Playback ID minted. | N/A |
failed | CF rejected the source (corrupt, unsupported codec, or webhook lost). | Yes: hit Retry in the uploads list, or delete + re-upload. |
Where playback URLs come from
Section titled “Where playback URLs come from”Signed HLS URLs are minted at play time by the mobile-facing API, not by the admin. The admin only stores the immutable playback_id. The signing service adds a short TTL and (optionally) IP scope so URLs can’t be shared.
Captions
Section titled “Captions”Once the video is ready, upload captions per language on the same episode:
- Media → Captions section on the episode editor
- Format: SRT or VTT
- One file per language (EN / AR / ID). The API stores each as a separate
media_assetwithkind=captionandlang=….
See Add captions for the deep dive.
Cliffhanger score
Section titled “Cliffhanger score”Every episode has a cliffhanger_score (0–100). Set it based on where the story leaves off:
0–30: episode wraps a beat cleanly.30–70: normal mid-arc episode.70–100: cliffhanger. The mobile app will never show a paywall or rewarded-ad prompt immediately after this episode (see Constants).
This is the single most important editorial call in the app. Get it right.
Custom thumbnails
Section titled “Custom thumbnails”CF auto-extracts three thumbnail candidates on transcode. If none look right:
- Click Upload custom thumbnail on the episode editor.
- Recommended 1280×720 (16:9), JPEG or WebP.
- Saved as a separate
media_assetwithkind=thumband linked asthumb_asset_idon the episode.
Gotchas
Section titled “Gotchas”- Webhook not received? Rare, but possible. The
media-synccron polls CF every minute for assets stuck inuploadingorprocessing> 10 minutes and reconciles state. You should never need to manually poke the API. - Uploading before the episode exists? You can upload from Media → Library without linking to an episode, then link later on the episode editor. The
media_assetwill haveepisode_id=nulluntil linked. - File too large? CF has provider limits (currently 30GB / 4 hours). The direct-upload endpoint refuses anything over your account’s plan cap.
- Wrong codec? CF re-encodes on ingest, but H.264/H.265 in an MP4 container is by far the most reliable input. AV1 sources sometimes fail: check the failure reason in the uploads list.
API calls behind this flow
Section titled “API calls behind this flow”| Action | Endpoint |
|---|---|
| Create direct upload | POST /v1/admin/media/uploads/direct |
| List assets | GET /v1/admin/media/assets |
| Retry failed asset | POST /v1/admin/media/assets/:id/retry |
| CF webhook (system) | POST /v1/admin/media/webhook/cf-stream (HMAC verified) |
| Link asset to episode | PUT /v1/admin/episodes/:id/media |
Full details in API: Media.