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.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-After — poll 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
Passpreview: "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).string
A download filename hint (used in
Content-Disposition)./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. HonorsRange
(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); thepin 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 livestatus 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: