Watermark removal (media.render) · cache-at-submit · posts requires max_items
New —
media.render: clean, downloadable video. A task that hands back a
watermark-clean copy of a platform video. Submit {source:"<platform>", action:"media.render", params:{item, clean, pin, filename}}; Torii resolves the source, keeps a metadata-stripped raw,
applies the clean recipe (meta · delogo:auto|<corner>|x:y:w:h|f:… · crop:… · keep:… ·
trim:a:b, composable), and serves the output by handle at GET /v1/media/render/<handle>
(Range, strong ETag, ?filename=). Every later recipe re-renders from the raw — no re-fetch, no
expired-CDN 403. Resubmitting an identical recipe is a source:"cache" hit. See
Watermark removal & rendering.New — media.detect. Localize a watermark in a stored handle with no re-encode:
{source:"media", action:"detect", params:{handle}} → {found, source:prior|vision, confidence, regions, frame}. found:false (looked, nothing) is distinct from detector_unavailable.Submit — cache-at-submit. A read that sets max_age_s and has a fresh-enough cached result is
now answered inline at POST /v1/tasks — 200 {status:"succeeded", source:"cache", result} —
instead of queuing behind the per-account lane. A miss still returns the usual 202 {task_id}; a
submit without max_age_s is unchanged. Check the response status before polling.Change (breaking) — posts requires max_items. Since the per-account no-cap change, an
omitted max_items on a .posts crawl means “the entire back-catalog” — a footgun for a
scheduler. POST /v1/tasks for posts now requires max_items: a positive count to bound the
walk, or -1 for the whole history. Absent or 0 → 400 invalid_request. max_items is now
advertised required on every .posts source in /v1/task-sources.Fix — douyin 抖音号. A numeric or alphanumeric 抖音号 (the account number shown on a profile,
e.g. 52955870542 / Blue_blue666) now resolves to its sec_uid for douyin.user / douyin.posts
instead of being mis-read as a video id and returning a silent empty. A non-account ref is a typed
error (invalid_request / not_found / blocked), never a nameless success.Fix — threads back-catalog. A threads.posts walk no longer stops at the first page of pure
reposts (it filters to authored-only); it walks past a bounded run of filtered pages to reach the
authored history.Crawl stats · media chunking · honest session diagnosis
New —
GET /v1/crawl-stats. A durable, per-crawl outcome log, aggregated per platform and
action: live / cache / empty / blocked / error counts, items, average latency, and the
first/last time each was seen. Unlike the result cache (successes only), this records empties and
blocks too — so a rising blocked rate is visible before it surfaces as failed requests. See
Analyzing outcomes.Media — large videos stream in Range chunks. GET /v1/media?kind=video for egress-bound CDNs
(YouTube’s googlevideo, Douyin) now fetches in bounded ≤1 MB Range requests, the way yt-dlp
does. This fixes the 403/502 you could hit on full-length videos. A client Range header is
honored (206 + Content-Range); without one you get the whole stream. No request change needed.Honest session_expired. A crawl on a platform whose account isn’t provisioned now returns
error.code: session_expired (previously it could look blocked, empty, or even live from
guest access). Branch on it as the accurate “this profile needs a human re-login” signal — distinct
from blocked. Applies across the stateful platforms (weibo, x, tiktok, douyin, …).Account failover across every read action. On an account-required platform with more than one
ready account, a blocked / session_expired / rate_limited result on one account is now
transparently retried on your other ready accounts (one at a time, never two sessions on one
account) — for every read action, comments included, not just search. blocked is returned
only when every account is walled; a single account’s search-wall or lapsed cookie no longer
surfaces as a failure. Failover is owner-scoped — it only ever uses accounts your bearer token
owns. The envelope’s warnings note how many accounts were tried. See
Account requirements.Capabilities — stateful on every source. GET /v1/task-sources rows now carry a stateful
boolean (true ⇒ the platform needs a provisioned social account; absent ⇒ open). It’s the account
analogue of the write flag, so you can gate up front instead of discovering the requirement from a
runtime session_expired. See Account requirements.Docs. Every task action now ships a sample response; the Sources
page documents which platforms need an account; there’s a
single-file, agent-readable skill doc at
/torii-api-agent.txt; and all examples use
the real base URL https://api.torii.t.bldr.tw.Eager previews · full-timeline pagination
Previews —
preview (eager) param. Video item verbs (search, posts, hot, …) accept
preview to pre-warm a scrubbable preview clip. The render pipeline was hardened: a result is
validated before it’s cached, and a still-rendering preview returns 202 + Retry-After (back
off and re-poll) rather than a broken clip. Terminal failures return 404.Pagination — max_items walks the full timeline. posts (and search) now page past the
first page on weibo, bilibili, and chzzk — previously they capped at ~1 page regardless
of max_items. Pages 2..N are paced by the request governor.Caching. The preview/media cache moved to a shared object store for the whole fleet. No
request-shape change; cache hits (source: "cache", with age_s) are now shared across nodes.