Search 6 hours of yourself in 300ms
Live demo of weaverclip_search_transcript.
SRT ⇄ VTT ⇄ TXT, overlap fixer, 2-line linter
All in browser — your file never leaves the page.
Vault + transcript + MCP — copy-paste, no credits to decode.
Private R2 vault (verified before delete), word-timed transcript (counts once, dual shares), 12 MCP tools. Full-stream SRT/VTT via toSrt() in lib/product/caption-export.ts — same transcript that powers 20/h suggestions. Pricing is derived from billing-catalog.json so page and Stripe never drift.
REST — Bearer token, service_role for embeds
All media is private R2, signed URLs expire in minutes
/api/media/sessions/[id]/manifest/api/clips/api/clips/bulk (via MCP weaverclip_bulk_create_edits)/api/ai-processing/jobs/api/usage/api/agent-reviews/api/shares/api/media/sessions/[id]/captions.{srt,vtt} (client toSrt)MCP — npx -y weaverclip-mcp (live) · npx -y @weaverclip/mcp after org
Also local: npm --prefix /path/to/weaverclip run mcp
- • weaverclip_list_sessions — list metadata, limit 1-100, no raw media
- • weaverclip_get_editing_manifest — transcript + markers + signed refs, manifestVersion 2, immutableSources
- • weaverclip_search_transcript — query 'pricing' across 6h in 300ms, returns [{startMs,endMs,text}] without downloading video
- • weaverclip_schedule_transcribe — startMs/endMs slice for 24h → save hours
- • weaverclip_bulk_create_edits — 18 at once, use after search
- • weaverclip_create_edit — single clip, layout center, preset Space etc.
- • weaverclip_get_usage — vault/transcript/renders remaining, same as dashboard meters
- • weaverclip_get_clip / weaverclip_get_clip_media — render state + signed URL after verify
- • weaverclip_add_marker — timecoded editorial marker, kind agent, score 0-1
- • weaverclip_submit_review — verified 1-5, Free counts, keeps host operating message
- • weaverclip_share_media — gallery link + optional email, Free 3 total
- • weaverclip_search_transcript is the MCP demo on /docs — live 300ms search
import { toSrt, toVtt } from "@/lib/product/caption-export";
toSrt(transcript) // transcript: { startMs, endMs, text }[]
// 1\n00:00:01,200 --> 00:00:03,400\nText
// WEBVTT\n00:00:01.200 --> 00:00:03.400
// use same transcript that powers 20/h ranked momentsInstall: npx -y weaverclip-mcp — keywords mcp,claude,codex,video,transcript. Also npx -y @weaverclip/mcp after org https://www.npmjs.com/org/create
Sharp nuances — what the API docs don't say until you need it
manifestVersion:2, immutableSources:true, sourceReferences with signedUrl per segment. Range with ?startMs&endMs for slices — so weaverclip_schedule_transcribe can ask for 18*3600000 to 24*3600000 without pulling 0-18h.search_vector tsvector on transcript_chunks and query with @@ plainto_tsquery. That means "pricing" matches "price" and "prices" with ranking, not just exact case. The 300ms is server-side, not client filter./api/clips/bulk will be one DB transaction — so a 12-hit bulk either queues 12 or queues 0, not 7 and then rate-limit. For a 24h subathon, that atomicity is the difference between 12 gallery links and 7 with 5 orphans.GET /api/usage returns the same workspace.usage the dashboard meters use — storageBytes/storageLimitBytes, processingHoursUsed/Included, renders, layouts. An agent that checks usage before bulk avoids the classic "I burned 4h on loading screens" mistake. Free never bills; it reports 0 remaining and the tool returns 429 if you try to transcribe over.The 3-lane model — what travels where, so you don't pile ffmpeg
Lane 1 Helper → R2: 1-min MKVs, byte count + MD5, signed PUT, HEAD verify, delete only on match. Lane 2 MCP = text only: sessions, transcript timecodes, markers, render status. MCP responses never contain video bytes — that's why agents can handle 24h without disk. Lane 3 Render → R2: clip decision → helper renders captioned MP4 with loudnorm+afftdn if Pro $29+, verifies, uploads. Video bytes move helper↔R2 and worker↔R2, never helper↔MCP↔agent memory.
How to turn a 3-hour sermon into captioned clips without watching it — the copy-paste recipe
- Upload the 3h MP4 via /upload-video or record via OBS — helper uploads 1-min segments while you talk.
- Call
weaverclip_get_usage— see “2.0h remaining on Free 2h” → you know a 3h needs Creator $15 for the extra hour, or pick the 2h sermon window. - Call
weaverclip_schedule_transcribewith startMs 0, endMs 7200000 — only that slice counts, not the 30 min of pre-service. - Poll
weaverclip_get_editing_manifestuntil transcript length hits ~120 cues. - Call
weaverclip_search_transcriptquery “grace” → 8 hits with startMs/endMs. - Call
weaverclip_bulk_create_editswith 8 edits — queued, helper renders 8× ~35 MB verticals with Space preset, returns clip IDs. - Call
weaverclip_get_clipper ID, thenweaverclip_get_clip_mediafor signed URL — email the gallery viaweaverclip_share_media(Free 3 total 30d).
Every step reuses the same transcript that powers toSrt() — download full-stream SRT at any time from /sessions/[id] without making a clip. That's the “plus the clips later” you asked for: upload once, get .srt now, clip next week.
What counts, and the exact moment it counts
Three meters, three different trigger moments. Every one of them fires on verified work, never on an attempt — which is why a failed upload, a failed transcription, and a failed render all cost nothing. Read the middle column as the answer to “when am I charged?” and the right column as the answer to “what happens if my worker retries?”
Counted when: The moment a segment passes byte-count and MD5 verification in R2.
The rule: Every stored source master counts its exact bytes. If both the 16:9 and the 9:16 master are stored, both count. Finished clips count too — a 45-second captioned vertical is roughly 35 MB.
Under retry: Reservations are serialized per account. read_user_storage_usage sums every segment across every variant, and reserve_source_segment_upload checks stored plus reserved bytes before it will sign another upload, so two lanes uploading at once cannot race past the limit.
Counted when: The moment a complete segment finishes transcription successfully.
The rule: A synchronized pair shares one show timeline. Transcripts, captions, and clip suggestions run against the primary source only, so one show minute counts once rather than once per aspect ratio. A 2-hour dual-canvas show is 2 hours of transcript and roughly double the storage.
Under retry: Failed minutes do not count. Re-watching, scrubbing, searching, and downloading SRT never re-bill. Three separate places enforce primary-only: queue_ai_processing_job reserves primary video, read_ai_processing_usage counts primary video, and a database trigger rejects any attempt to move a non-primary segment into a queued, processing, complete, or failed AI state.
Counted when: The moment a claimed render is checksum-verified and atomically completed.
The rule: Each checksum-verified finished export counts as one render. Creating both a vertical and a landscape export of the same moment counts as two renders — same show minute, two finished files.
Under retry: complete_verified_render takes a row lock, and only the rendering → ready transition inserts render_usage. A replayed completion for a clip that is already ready returns without incrementing anything, so a retrying worker cannot double-bill. A render that fails verification never counts at all.
The short version for anyone building on top of this: running two OBS canvases does not spend plan allowance by itself. WeaverClip meters files and completed work, not canvases. Two masters is two files of storage. One show is one show of transcript. Two exports is two renders. Nothing about that changes if you add a third canvas, and nothing about it changes if you never export at all.
Plan ceilings, rendered from the catalog
This table is generated from lib/product/billing-catalog.json at build time — the same file the Stripe bootstrap reads. There is no second copy of these numbers anywhere in the codebase, so a page cannot quote a price that Stripe does not charge. Hours are computed with the one constant that governs all of this: 1 Mbps is 0.45 GB per hour.
| Plan | Price | Vault | Hard limit | At 6 Mbps | At 12 Mbps | Transcript | Renders | Sources kept |
|---|---|---|---|---|---|---|---|---|
| Free | $0 | 10.0 GB | same — Free never bills | ~4h | ~2h | 2h | 6 | 30d |
| Creator | $15/mo | 75.0 GB | 150 GB | ~28h | ~14h | 4h | 100 | 180d |
| Creator Pro | $29/mo | 150 GB | 300 GB | ~56h | ~28h | 8h | 300 | 180d |
| Studio | $119/mo | 1.50 TB | 3.00 TB | ~556h | ~278h | 70h | 2000 | 180d |
Above the vault: $0.04/GB-month and $0.05 per extra render. Transcript hours are deliberately absent from that list — going over pauses new transcription until renewal or upgrade, and never produces a charge. That asymmetry is intentional: storage and renders are things you chose to keep, transcript is a thing you might trigger by accident on a 24-hour stream.
The hard limit is 2× the vault on every paid plan, and it is a rail rather than a trap. On Creator you can burst to 150 GB while paying $0.04 per GB-month for the excess — the seventy-sixth gigabyte costs four cents for the month, not a forced upgrade. At the ceiling, uploads pause and files stay on the recording computer. WeaverClip never deletes stored footage to make room for new footage.
A paired show, over the API
When someone records a 16:9 canvas in OBS and a 9:16 canvas in Aitum, WeaverClip does not see two recordings. It sees one show with two source masters, modelled as rows in recording_variants. Every session has exactly one primary variant, and segment uniqueness includes the variant — which is why two lanes can both upload sequence 0 without one overwriting the other.
unverified_range, not a clip stitched from two timelines.Deletion follows the same shape. Retention and owner-requested deletion enumerate segment objects by session, so every variant goes with the source recording. Finished clips keep their own lifecycle, which is the practical reason a transcript and its clips can outlive the master that produced them.
Error contract — read the code, not the status
Every failure returns the same envelope: { error: { code, message } }. The code is stable and safe to branch on. The message is written for a human and names a cause and a next action — it is the same sentence a creator would see, which is why there is no “something went wrong” anywhere in this API. Group your handling by family, not by individual code.
402 — the account is out of room, not broken
What to do: Stop asking for more. Tell the human what to delete or upgrade. Keep the local file.
storage_hard_limit_reached— The vault is at its hard limit (2× the plan vault). The response says how many GB are actually free. The recording stays on the Mac and the helper keeps retrying with backoff — nothing is deleted to make room.processing_allowance_exceeded— The selected range needs more transcript hours than the month has left. Narrow the range and call again; WeaverClip processes whole segments, so a range that clips a segment still spends that whole segment.processing_unavailable— New transcript, caption, and suggestion work is paused for the period. Existing transcripts stay readable and searchable.billing_read_only— Cloud uploading is paused because the account is read-only. The helper keeps the recording locally rather than dropping it.layout_credits_exhausted— No layout generations left this month. Packs are 10 for $6 or 30 for $15.
409 — verification said no, and that is the feature
What to do: Retry with backoff for the transient ones. Never delete the local file first.
checksum_mismatch— The bytes in R2 do not match the bytes on disk. The local segment must be kept and uploaded again. This is the check that lets the helper delete safely.object_unavailable— R2 accepted the write but the object is not readable yet. Keep the local file and retry — this one is genuinely transient.segments_not_verified— A finish was attempted while some prepared segment had not passed verification.segment_gap— The prepared segments contain a time gap. The session is not continuous, and WeaverClip will not pretend it is.unverified_range— The requested source does not fully cover the range with checksum-verified footage. Pick a range inside verified footage.transcript_required— A captioned render was requested over a range with no transcript. Process the range first.render_checksum_mismatch— A render claimed completion but its artifact failed verification. The previous good artifact was not replaced, and no render was counted.stale_render_claim / stale_processing_claim— Another worker already took or finished this lease. Drop the claim; do not re-submit.clip_not_ready— The clip exists but has not passed render verification, so there is no media reference to hand out yet.
410 — the source is gone on purpose
What to do: Do not retry. Retention ran. Fall back to the transcript, which outlives the video.
source_recording_expired— The master reached its scheduled deletion date and was deleted. Free keeps sources 30 days, paid plans 180.source_expired— The source can no longer be processed.
400 / 401 / 403 / 404 — fix the request
What to do: These are deterministic. Retrying the identical call returns the identical error.
invalid_request— Zod rejected the body. The message lists every failing field, joined by semicolons — read it, do not guess.range_outside_recording— The end time is past the end of the recording.invalid_token / not_authenticated— Bearer token expired, or no session. Helper tokens are Supabase user tokens, not API keys.forbidden— The recording belongs to another account. RLS is the boundary; the API is not the boundary.recording_variant_not_found— The requested source (landscape or vertical) is not registered on this show.
The pattern worth internalising: a 409 almost always means “your local copy is still the good copy”. It is the vault refusing to confirm something it has not verified. An integration that treats 409 as fatal and deletes its working file has thrown away the exact thing the error was protecting.
Agent disk hygiene — upload, verify, then delete
The reason agents can work on twenty-four hours of footage from a laptop is that no video ever enters the agent’s context or its disk for longer than one segment. The loop is always the same three steps, in that order, with no shortcuts:
- Upload. Write to a temporary path, request a signed PUT, send the bytes. The vault is private R2; signed URLs expire in minutes and nothing is ever public.
- Verify. HEAD the object and compare byte count and MD5 against what was on disk. This is not optional and it is not a “probably fine” check — it is the single gate that separates a safe delete from data loss.
- Delete the temp file. Only after the comparison matches. On mismatch you get
checksum_mismatch, the temp file stays, and the upload retries with backoff.
Doing this in the wrong order is the failure mode that fills a host disk, and doing rendering in the wrong place is the failure mode that melts it. Do not pile ffmpeg onto the host running the agent. An agent issues a clip decision; the helper renders it, verifies it, uploads it, and reports back a clip ID. That separation is what the three-lane model above is for, and it is why an agent orchestrating eighteen renders uses roughly the same memory as one orchestrating a single render.
Agent workspaces get their own isolated vault behind a scoped API key and R2 prefix. An agent’s footage never appears in a human’s vault, and a human’s footage is never visible to an agent that was not granted it. Free is 10 GB and 2 hours, which is enough for a few real test runs a week, and Free never bills — it pauses.
Knowing when work is finished
There are no webhooks to subscribe to yet, and polling is deliberately cheap because the interesting responses are text. A clip moves through queued, rendering, and then either ready or failed. Poll weaverclip_get_clip for the state and only call weaverclip_get_clip_media once it is ready — asking earlier returns clip_not_ready rather than a URL that would break.
Transcription is the same shape from the other direction: call weaverclip_get_editing_manifest and watch the cue count grow. A two-hour recording settles at roughly one hundred and twenty chunks, one per minute, each with word-level startMs and endMs. Because segments are transcribed independently, a partially transcribed session is still fully searchable over the part that finished — you do not have to wait for hour twelve to search hour one.
Call weaverclip_get_usage before any bulk operation. It returns exactly what the dashboard meters show, and it is the difference between an agent that queues eighteen renders and one that queues four and then reports honestly that the month has four renders left. An agent that checks first can also make the good decision on a long recording: transcribe the ninety minutes where someone actually talks, not the six hours of loading screens around them.
Features deep dives: transcription (counts once) · captions (5 presets + Space) · storage (verified before delete) · Use cases: sermons · podcasts · 24h streams · pricing · vs OpusClip calculator

