Caption a video from Claude Code
This is one real session, start to finish: a 30-second 720×1280 clip captioned from the terminal with the CapsAI connector, using nothing but Claude Code and curl. Tool output is the session's own, trimmed, with signatures and tokens removed. You need Claude Code and a CapsAI account (a new email creates one when you sign in).
1. Add the connector
Register the CapsAI MCP server once, then sign in from inside Claude Code:
claude mcp add --transport http capsai https://mcp.capsai.co/mcpIn a Claude Code session run /mcp, pick capsai and sign in with your CapsAI email and the code it sends you. That is the whole setup; the claude.ai steps are on the connector page if you also want it in the app.
2. Pick a style
Ask Claude to show the CapsAI caption styles, or call list_caption_styles. Each style comes back with its id, a pro flag and whether it is available on your plan:
{
"styles": [
{ "id": "poppins", "name": "Pop Out", "animation": "push-pop", "pro": true, "available": false, ... },
{ "id": "fp-story", "name": "Story", "animation": "fade up", "pro": false, "available": true, ... },
...
],
"defaultStyle": "fp-tommy-soft",
"positions": ["top", "middle", "bottom"],
"note": "Any style can be made static with animated=false."
}The id is what you pass as style. This session used fp-story, a free one. All styles with previews are on the styles page if you would rather look before you choose.
3. Start the job
Tell Claude what to caption and how. It calls caption_video with the file name, the exact size in bytes, the spoken language, the style, and how the captions should sit:
{
"file_name": "dev-sample-sync.mp4",
"file_size_bytes": 3030229,
"language": "en",
"style": "fp-story",
"animated": true,
"words_per_caption": 2,
"position": "bottom"
}Chat attachments are not passed to this tool, so the result is a project plus a presigned upload you can drive yourself. upload.curl is ready to run; only the file path is missing:
{
"projectId": "59e7e588…",
"status": "waiting_for_upload",
"upload": {
"method": "PUT",
"url": "https://storage.googleapis.com/…/1791690098_bc27….mp4?X-Goog-Algorithm=…&X-Goog-Expires=3599&X-Goog-Signature=<redacted>",
"headers": {
"Content-Type": "video/mp4",
"x-goog-content-length-range": "3030229,3030229",
"x-goog-if-generation-match": "0",
"x-goog-custom-time": "2026-10-12T03:41:38Z"
},
"curl": "curl -X PUT --upload-file <path-to-video> -H 'Content-Type: video/mp4' -H 'x-goog-content-length-range: 3030229,3030229' … '<upload.url>'",
"maxBytes": 500000000
},
"uploadPageUrl": "https://mcp.capsai.co/upload/<token>",
"settings": { "stylePreset": "fp-story", "animated": true, "wordsPerCaption": 2, "position": "bottom" },
"editorUrl": "https://capsai.co/editor?projectId=59e7e588…"
}4. Upload with curl
Run upload.curl with the path filled in. The headers must be sent exactly as given; the signed URL is bound to them, and x-goog-content-length-range pins the upload to the byte count you passed as file_size_bytes:
curl -X PUT --upload-file public/dev-sample-sync.mp4 \
-H 'Content-Type: video/mp4' \
-H 'x-goog-content-length-range: 3030229,3030229' \
-H 'x-goog-if-generation-match: 0' \
-H 'x-goog-custom-time: 2026-10-12T03:41:38Z' \
'<upload.url>'
# HTTP 200, 3030229 bytes uploaded in 1.6 sIf you would rather not script the upload, uploadPageUrl opens a browser page for the same project.
5. Poll until it is done
check_caption_job reports the stage and moves the job along: transcription first, then the render. Each result says when to check again.
{ "projectId": "59e7e588…", "status": "transcribing", "next": "Upload received. Check again in 20 seconds." }
{ "projectId": "59e7e588…", "status": "rendering", "progress": 0.9, "next": "Check again in 30 seconds." }
{
"projectId": "59e7e588…",
"status": "done",
"style": "fp-story",
"downloadUrl": "https://mcp.capsai.co/v/<id>",
"downloadAvailableUntil": "2026-10-12T03:41:38+00:00",
"editorUrl": "https://capsai.co/editor?projectId=59e7e588…",
"next": "Share downloadUrl with the user. It keeps working until downloadAvailableUntil, always serving the latest render."
}Then download it:
curl -L -o captioned.mp4 'https://mcp.capsai.co/v/<id>'The file is the clip with the captions burned in, in the style you asked for. The preview below shows what the styles look like on a video; it is the same engine.
See Your Captions in Action
Try a style and language for your next video.

What it costs, and the limits
Transcription uses your CapsAI minutes, one per minute of video, so this 30-second clip used half a minute. The first render of a project is included; rendering it again in another style costs the video's length again. get_account reports the balance and the limits for your account. As of 11 October 2026 this one looked like this after the job:
{
"minutesLeft": 57.07,
"plan": "free",
"maxVideoMinutes": 10,
"maxUploadSize": "500 MB",
"maxExportHeight": 1080,
"buyMinutesUrl": "https://capsai.co/pricing"
}Run get_account yourself for current numbers; plans change and the connector always reports the ones that apply to you. The connector takes mp4, mov, m4v and webm files. It does not fetch videos from a link, so download first and upload the file. The same facts are kept up to date under styles, minutes and limits.
After the first render
- Change the look without uploading again.
restyle_videotakes the sameprojectIdwith a newstyle,animated,words_per_captionorposition; settings you leave out keep their value. - Fix a word or a timing.
editorUrlopens the project in the CapsAI editor. - Find it later.
list_videoslists your recent projects, newest first, with the same editor links.
Set up the connector
One command in Claude Code, or five clicks on claude.ai. Then ask Claude to caption a video.
Connect CapsAI to ClaudeQuestions
Can Claude Code upload the video itself?
Yes. The caption_video result includes a presigned PUT (upload.url, the exact upload.headers and an equivalent upload.curl). Claude Code can run that curl with the file path, so nothing has to be dragged into a chat. On claude.ai the same job shows an upload card instead.
Why does the file size have to match exactly?
The presigned upload is bound to the file_size_bytes you passed to caption_video (the x-goog-content-length-range header carries the same number twice). Pass the real byte count, or omit file_size_bytes and let the upload page handle it.
Does the download link expire?
The check_caption_job result gives the link a downloadAvailableUntil time. Run check_caption_job again for a fresh link; it always serves the latest render of that project.
Which styles can I use on the free plan?
list_caption_styles marks each style pro: true or false and available: true or false for your account. Pro styles need a paid plan; the free ones render straight away. Any style can also be rendered as plain static subtitles with animated: false.
Related
CapsAI for Claude
Set up the connector on claude.ai, the desktop app or Claude Code, and what to ask once it is on.
Caption styles for Claude
Every style the connector renders, with previews, Free or Pro, and the id to name in a prompt.
Caption Generator
The web editor, for fixing words and timing after Claude has captioned the video.