Skip to content
What did I say about ___?

Search 6 hours of yourself in 300ms

Live demo of weaverclip_search_transcript.

Caption File Toolkit

SRT ⇄ VTT ⇄ TXT, overlap fixer, 2-line linter

All in browser — your file never leaves the page.

Word-timing validator: each cue < 3s, < 42 chars/line for 9:16.
Docs — single source of truth is billing-catalog.json

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

GET/api/media/sessions/[id]/manifest
Immutable source metadata + transcript_chunks (word-timed) + markers + short-lived signed media refs (R2). Raw video never in payload. Range with ?startMs&endMs for slices.
POST/api/clips
Create clip edit: sessionId, startMs, endMs, title, hook, aspect 9:16/16:9/1:1, preset impact/clean/block/story/space, captionConfig {accent, scale, baseline, sourceFit, audioEnhanced (Pro $29+)} — queued, not overwriting source.
POST/api/clips/bulk (via MCP weaverclip_bulk_create_edits)
Up to 18 edits in one call. Use after weaverclip_search_transcript to turn 12 hits into 12 queued renders without 12 round-trips.
POST/api/ai-processing/jobs
Schedule transcribe for a slice: sessionId, startMs, endMs. For 24h stream, schedule last 6h not all 24h to save transcript hours. Called by weaverclip_schedule_transcribe.
GET/api/usage
Vault used/limit, transcript used/included/remaining, renders used/allowance, layouts. Agents call before picking a range — '1.2h left, pick 1h not 2h'.
POST/api/agent-reviews
Verified run report: rating 1-5, task, model, runMs, note 500ch — one per session, max 5/day, Free counts only if session was stored.
POST/api/shares
Gallery link: sessionId or clipId → token, emailTo optional. Free 3 total 30d, paid unlimited 180d. Email is a link, not 65 GB attachment.
GET/api/media/sessions/[id]/captions.{srt,vtt} (client toSrt)
Full-stream SRT/VTT stitched from transcript_chunks — same chunks that power 20/h suggestions. Download without making a clip.

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 moments

Install: 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

Manifest is immutable. 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.
Transcript search is tsvector, not substring. We store 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.
Bulk is not just a loop. The MCP tool loops 18 calls today, but the future /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.
Usage is the dashboard truth. 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

  1. Upload the 3h MP4 via /upload-video or record via OBS — helper uploads 1-min segments while you talk.
  2. 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.
  3. Call weaverclip_schedule_transcribe with startMs 0, endMs 7200000 — only that slice counts, not the 30 min of pre-service.
  4. Poll weaverclip_get_editing_manifest until transcript length hits ~120 cues.
  5. Call weaverclip_search_transcript query “grace” → 8 hits with startMs/endMs.
  6. Call weaverclip_bulk_create_edits with 8 edits — queued, helper renders 8× ~35 MB verticals with Space preset, returns clip IDs.
  7. Call weaverclip_get_clip per ID, then weaverclip_get_clip_media for signed URL — email the gallery via weaverclip_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?”

Storagebytes, decimal GB

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.

Transcript hourssource hours

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.

Renderscount

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.

PlanPriceVaultHard limitAt 6 MbpsAt 12 MbpsTranscriptRendersSources kept
Free$010.0 GBsame — Free never bills~4h~2h2h630d
Creator$15/mo75.0 GB150 GB~28h~14h4h100180d
Creator Pro$29/mo150 GB300 GB~56h~28h8h300180d
Studio$119/mo1.50 TB3.00 TB~556h~278h70h2000180d

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.

Pairing is by filename stem, never by clock. The helper pairs a vertical file with a landscape file only when the first stem of the filename matches exactly. Extensions may differ. A vertical file whose stem does not match the active landscape key stays on the Mac and is reported in helper logs — it is never attached because it happened to finish at a similar time. Two recorders started thirty seconds apart produce two shows, not one mislabelled one.
Registration is idempotent across restarts. The active session, pair key, server variant IDs, the registration request itself, retry backoff, per-lane clocks, and file checksums all live in the helper journal. If the helper dies mid-registration, it re-sends the byte-identical request on restart and takes the API’s idempotent response. You will not get a duplicate variant because a laptop lid closed.
Every lane verifies its own bytes. Each lane keeps an independent sequence number and source-time clock. A file stays local until its own variant-specific object passes byte-count and checksum verification — the landscape master being safe says nothing about the vertical master being safe, and the UI never claims otherwise.
9:16 source selection is explicit about what it did. Automatic selection uses the native vertical master only when it shares the primary source clock and covers the entire selected range. Otherwise it falls back to the primary master and records the reason on the clip. A specifically requested source with a clock mismatch or coverage gap is rejected outright rather than silently mixing masters — you get 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_reachedThe 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_exceededThe 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_unavailableNew transcript, caption, and suggestion work is paused for the period. Existing transcripts stay readable and searchable.
  • billing_read_onlyCloud uploading is paused because the account is read-only. The helper keeps the recording locally rather than dropping it.
  • layout_credits_exhaustedNo 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_mismatchThe 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_unavailableR2 accepted the write but the object is not readable yet. Keep the local file and retry — this one is genuinely transient.
  • segments_not_verifiedA finish was attempted while some prepared segment had not passed verification.
  • segment_gapThe prepared segments contain a time gap. The session is not continuous, and WeaverClip will not pretend it is.
  • unverified_rangeThe requested source does not fully cover the range with checksum-verified footage. Pick a range inside verified footage.
  • transcript_requiredA captioned render was requested over a range with no transcript. Process the range first.
  • render_checksum_mismatchA 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_claimAnother worker already took or finished this lease. Drop the claim; do not re-submit.
  • clip_not_readyThe 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_expiredThe master reached its scheduled deletion date and was deleted. Free keeps sources 30 days, paid plans 180.
  • source_expiredThe 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_requestZod rejected the body. The message lists every failing field, joined by semicolons — read it, do not guess.
  • range_outside_recordingThe end time is past the end of the recording.
  • invalid_token / not_authenticatedBearer token expired, or no session. Helper tokens are Supabase user tokens, not API keys.
  • forbiddenThe recording belongs to another account. RLS is the boundary; the API is not the boundary.
  • recording_variant_not_foundThe 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:

  1. 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.
  2. 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.
  3. 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