Platform CDN URLs are session-signed and expire within hours — and fetching one from a consumer’s own host is un-governed egress that often 403s. So Torii hosts every media URL: an Item’s thumb and media[].url point at /v1/media, and a video item also carries a small /v1/preview clip.

Media proxy

Streams a platform CDN image or video through Torii, from the same egress that resolved it. Public — an <img> or <video> can point straight at it. The url values inside an Item are already in this shape; you rarely build one by hand.
string
The origin CDN URL (present in the Item’s media URLs, allowlisted).
string
video routes to the node that holds the session.
string
The platform (for session-gated video).
string
Optional ffmpeg pass — strip metadata / erase a logo, using the clean recipe grammar. For a durable, re-runnable clean copy prefer media.render.
Range requests are honored (206), so a <video> can seek.

Preview clips (hover-to-scrub)

A low-res, muted, keyframe-dense, full-length clip for a video item — the asset that makes a hover-to-scrub card (cursor X → playback position) cheap without streaming the full media. It is the whole clip, capped at 240 s; a keyframe every ~1 s makes seeking cheap.
Public. Point a <video> straight at it. Returns 200 video/mp4 (h264 baseline, short side 240 px, no audio, +faststart so the duration is readable from the head), 206 on Range. Because a full-length render is heavy, the cold path is asynchronous: a first request for an uncached clip returns 202 {"status":"rendering"} with a Retry-Afterpoll the same URL until it’s a 200. A X-Torii-Preview: hit|rendered header tells you which on a 200. Cached 14 days.
string
required
The platform.
string
required
The bare item id (from Item.id minus the platform: prefix).
Item.preview.seconds reports the rendered length (the clip’s duration, capped at 240) — map cursor→time against it, or read the served mp4’s own duration. The Item.preview.url is already this path and is stable across crawls (keyed on platform + id, not the rotating CDN URL). When a clip can’t be rendered, it returns:
404
reason is one of expired | blocked | session_expired | no_stream | unsupported — fall back to the cover, and the reason tells you why. Never a placeholder clip.

Warm a page

Render (in the background) the previews for a page you’re about to show, so the first hover is a cache hit. Requires a token (a server-to-server trigger).
Request
202 Accepted

Render at crawl time

Pass preview: "eager" in a crawl’s params (for platforms whose CDN URL expires fast, like TikTok/Douyin) and Torii renders each new video item’s preview at crawl time — before the URL dies — bounded and best-effort, never delaying the crawl.

Watermark removal

media.render is a task that returns a clean, downloadable copy of a platform video. You submit a platform item and a recipe; Torii resolves the playable source, keeps a metadata-stripped raw, applies the recipe, and serves the result by handle. Every later recipe for the same item re-renders from that raw — no re-fetch of the platform, no expired-CDN 403.
It’s a normal task: submit → persist the task_id → collect when it’s done. A render is a fetch plus an ffmpeg pass, so it runs on Torii’s clock — poll (or stream), never hold a socket waiting.

Submit

Request
string
required
The platform item id or a post URL — Torii resolves it to the video.
string
The recipe (see the grammar). Empty ⇒ meta (strip metadata only).
boolean
Keep the output and its raw — see retention.
string
A download filename hint (used in Content-Disposition).
Advertised per video platform in /v1/task-sources as <platform>.media.render (douyin, tiktok, weibo, youtube, bilibili, instagram, threads, facebook, x).

The clean recipe

One string, comma-composable; applied trim → delogo → crop in source coordinates. This is also the grammar of the /v1/media?clean= proxy param.

Collect

GET /v1/tasks/{id} — a succeeded task’s result is the render verdict:
result — douyin.media.render (clean: "delogo:auto")
boolean
true on a successful render — including cleaned:"none".
string
live (rendered now) or cache (an identical (item, clean) was already rendered — served in milliseconds).
string
Delivery handle for the output: render/<platform>/<item>/<spec-sha>.
string
Handle for the metadata-stripped source — offer it as an “(original)” download.
string
applied (a watermark op ran) or none (nothing to erase). none is a success — the file is real and kept; say “still watermarked”, don’t error.
string[]
The box(es) acted on, as source-frame fractions "x,y,w,h". Always present (empty for meta or an explicit box).
string
prior (a deterministic known position) or vision (the model found it); empty for a non-auto verb.
number
The model’s confidence for a vision region (absent for a prior).
object
{ w, h } of the output (a crop/keep changes it).
integer
Output duration (a trim changes it).
integer
Output size in bytes.
string
Content hash of the output — also the delivery ETag.

Download by handle

Streams the output (or the raw) straight from Torii’s store. Requires a token. Honors Range (206 + Content-Range), sends a strong content-hash ETag, and — with ?filename= — a Content-Disposition so a browser download names the file. The path is the handle verbatim: GET /v1/media/render/douyin/7412…/9f1c….

Detect a watermark (no re-render)

Localize the watermark in a stored handle with no re-encode — for a “show me the box” editor.
Request
result
found:false (looked, saw nothing) is distinct from the detector_unavailable error (the vision model is down). A platform with a deterministic prior (weibo) answers source:"prior", no model needed.

Idempotency & the raw

(platform, item, clean) identifies a render: resubmitting the identical recipe returns the same handle with source:"cache" in milliseconds. The raw is fetched once per item — the first media.render (any recipe) fetches and stores it; every later recipe renders from that raw, so changing a clip’s recipe never re-touches the platform (and never hits an expired CDN URL).

Retention

A render and its raw are kept for now (about the preview-cache lifetime); the pin flag and an unpinned-TTL sweep are landing next (they need a store primitive still in flight). Mirror any handle you must keep into your own storage.

Outcomes

media.render uses the standard typed error model plus render-specific codes. Each is a settled answer on a succeeded task (ok:false), not a failed job.

Account health

Lists the stored accounts and their live status per platform (ready, session_expired, …). This — not a per-task error.code — is the signal to prompt a re-login: a task’s blocked is a transient challenge, whereas /v1/profiles reporting session_expired means the account genuinely needs a human to log back in.

Preview cache stats

Beyond liveness, /v1/health surfaces the preview cache counters so you can graph its effectiveness: