Templates

Add Captions

POST/v1/caption-videos

Caption a video

Send the video as multipart form data, or a url for Vsub to fetch. Credits are charged the same way as videos uploaded in the app.
Body
file
file
The video itself, sent as multipart form data under the field name file. Up to 500MB and up to 2 hours long. Pass either this or url.
url
string
Where to fetch the video from instead. The url has to be reachable without authentication, and points at the file itself, not at a page playing it. Same size and length limits as an upload.
templateId
number
Caption style the words on screen use. Built in styles have a negative id, styles you saved in the app have a positive one, list them with Caption templates. Left out, the video uses the style your workspace used last.
render
object
Export the mp4 as soon as the captions are timed, taking the same fields as the render endpoint, see render fields. Pass an empty object to accept the defaults. Left out, the video stays an editable project you can render later. Rendering charges export credits and requires a paid plan.
webhookUrl
string
HTTPS callback fired once the video reaches a terminal status. See Webhooks for details.
render fields
render.format
"standard" | "greenscreen"
Standard renders the finished video. Green screen drops the background and renders the captions and overlays on a solid green fill, ready to key out in your own editor.
default: standard
render.frameRate
30 | 60
Frames per second. Rendering costs 30 credits per minute of video at 30fps and 60 credits per minute at 60fps.
default: 30

Form data carries every field as text, so when you send a file pass templateId as a plain number string and render as a json string. Fetching from a url takes an ordinary json body.

Request
curl -X POST "https://api.vsub.io/v1/caption-videos" \
  -H "Authorization: Bearer vp_a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90" \
  -F "file=@podcast-clip.mp4" \
  -F "templateId=-2"

# or point it at a url instead
curl -X POST "https://api.vsub.io/v1/caption-videos" \
  -H "Authorization: Bearer vp_a1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/clips/podcast-clip.mp4",
    "templateId": -2,
    "webhookUrl": "https://your-app.example.com/hooks/vsub-video"
  }'
Response 200
{
  "id": "9f0c1d5e-3a1b-4c2f-9d7e-8b1a2c3d4e5f",
  "status": "processing",
  "render": false,
  "createdAt": "2026-08-05T09:20:11.000Z",
  "editorUrl": "https://vsub.io/workspace/editor/9f0c1d5e-3a1b-4c2f-9d7e-8b1a2c3d4e5f"
}

Uploading and transcribing run in the background. Follow the video with Video status or let a webhook tell you when it's done.

More examples

Captioned and exported in one go

Adding render exports the mp4 as soon as the captions are timed, so the webhook fires once with a finished video rather than with a project waiting in the editor.

Body
{
  "url": "https://your-app.example.com/clips/podcast-clip.mp4",
  "templateId": -8,
  "render": { "frameRate": 60 },
  "webhookUrl": "https://your-app.example.com/hooks/vsub-video"
}

Everything the request doesn't cover, such as fixing a misheard word, moving the captions or swapping the style, is still yours to change in the editor before rendering. Uploads follow the storage rules of your plan, so on plans where files expire the source video stops resolving after the same number of days as one uploaded in the app.