How to Auto-Generate Subtitles Using an API
By Jessica B
· 6 minutes read
Every short-form video needs captions now. Most people watch on mute, platforms push clips with readable on-screen text, and a video without captions gets scrolled past in a second. Building that capability in-house means wiring up speech recognition, a render queue, and ffmpeg, then keeping all three alive. A video captioning API removes that work. You send a clip, pick a style, and a finished captioned video comes back.
This guide walks through how to add captions to a video using an API in three calls. By the end you will have a working flow that takes a raw clip and returns a styled video with the words baked in. The examples use ZapCap, and the pattern carries over to any captioning service built around a task model.
What You Need Before You Start
Three things, plus one that helps:
- An API key. You create it and load render credits in the dashboard at platform.zapcap.ai
- A video. Either a hosted URL or a local file you upload directly.
- An output format choice: burned-in, transparent overlay, or green-screen. The section further down explains each.
- A webhook endpoint, optional. ZapCap can call your server the moment a render finishes, so you skip the polling.
Have your key ready and a test clip hosted somewhere reachable. The full reference lives in the ZapCap API docs.
How to Add Captions to Video using API: Step by Step
Here's how a captioning API works: you upload the video, ask for a render, and grab the result once it's done. Everything in between, the transcription, the translation, the layout, the animated styling, happens on ZapCap's servers.
Step 1 — Upload Your Video
Every caption job starts by getting your video onto ZapCap's servers. There are two ways to do it: point the API at a video that's already hosted somewhere public (an S3 bucket, a CDN, any reachable URL), or upload a local file directly. The hosted-URL path is the simplest, so we'll use it here.
Send a POST request to the /videos/url endpoint with the video's URL in the body. Your API key goes in an x-api-key header, and since the body is JSON, set Content-Type to application/json. The request looks like this:
bash
curl -X POST https://api.zapcap.ai/videos/url \
-H "x-api-key: $ZAPCAP_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://cdn.example.com/clip.mp4"}'
ZapCap responds with a JSON object that includes an id — this is your video ID, and every call from here on refers back to it:
json
{ "id": "8f2a9c14-3b7e-4a21-9c5d-1f0e2d3a4b5c" }
Save that id. In the steps below it's referred to as $ID, and you'll pass it into the task call in Step 2.
Uploading a local file instead? Send a multipart form to /videos (note: no /url) with the file attached as a file field. The response is the same shape - you get back an id to reuse.
Step 2 — Create the Caption Render Task
With your video uploaded and its id saved as $ID, this next call kicks off the real work - transcription, styling, and rendering - by creating a task against that video.
First you need a templateId, which is the caption style: the animated look, font, and highlight behavior. Each template has its own UUID and you can't guess them, so pull the list from the /templates endpoint and pick one:
bash
curl https://api.zapcap.ai/templates \
-H "x-api-key: $ZAPCAP_KEY"
That returns an array of templates, each with an id:
json
[
{ "id": "a51c5222-47a7-4c37-b052-7b9853d66bf6", "name": "Hormozi 1" }
]
Now create the task. You pass the template ID, the language spoken in the video, and — if you're using webhooks — a notification block telling ZapCap where to call you when the render finishes:
bash
curl -X POST https://api.zapcap.ai/videos/$ID/task \
-H "x-api-key: $ZAPCAP_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "a51c5222-47a7-4c37-b052-7b9853d66bf6",
"language": "en",
"autoApprove": true,
"notification": {
"type": "webhook",
"notificationsFor": ["render"],
"recipient": "https://example.com/hooks/zapcap"
}
}'
The response contains a taskId that identifies the render job. You'll use it to receive or poll for the result:
json
{ "taskId": "3f9b7c02-8a41-4d6e-b2c9-5e1f0a2d3c4b" }
Save it as $TASK_ID for Step 3.
One field is doing critical work: autoApprove: true. By default, a task pauses after transcription and waits for you to manually approve the transcript before it renders. Leave this flag out and your video transcribes and then just sits there - rendering never starts, and the webhook you're waiting on never arrives. Setting autoApprove: true sends it straight through to rendering. Include it unless you specifically want to review or edit the transcript first.
The other fields, briefly: templateId is the style you pulled above; language is the language spoken in the clip, which tells the transcriber what to expect (it does not translate - captioning in a different language than the audio is a separate translateTo parameter); and notification is your webhook config, where notificationsFor: ["render"] means "ping me when the render is done" and recipient is your endpoint. Omit the whole notification block if you plan to poll instead.
Step 3 — Receive the Finished Video
Rendering runs asynchronously on ZapCap's side, so this step is about getting the finished file once it's ready. You have two options: let ZapCap call you when it's done (webhooks), or check on an interval yourself (polling). Webhooks are the recommended path - no wasted requests, and you hear the moment the render lands.
If you included a notification block in Step 2, ZapCap sends a POST to your recipient URL when the render completes:
{
"eventId": "evt_9c143b7e4a21",
"event": "completed",
"taskId": "3f9b7c02-8a41-4d6e-b2c9-5e1f0a2d3c4b",
"notificationFor": "render",
"renderUrl": "https://storage.zapcap.ai/renders/…"
}
The finished video is at renderUrl. Two things to keep in mind: it's a presigned link that expires after 60 minutes, so download the file promptly rather than saving the URL for later - and because ZapCap retries delivery up to five times if your endpoint doesn't return a 200 fast enough, the same event can arrive more than once. Use the eventId to deduplicate, acknowledge with a 200 within about 300 ms, and do the download asynchronously. If the render fails, event comes back as "failed" with no renderUrl.
If your stack can't accept inbound callbacks, skip the notification block and poll the task instead:
curl https://api.zapcap.ai/videos/$ID/task/$TASK_ID \
-H "x-api-key: $ZAPCAP_KEY"
This returns the task's current status, and the download link once it's done:
json
{ "status": "completed", "downloadUrl": "https://storage.zapcap.ai/renders/…" }
The status field moves through transcribing → rendering → completed (or failed), so loop every few seconds until you see completed, then grab the file. One thing to watch: when you poll, the finished video is under downloadUrl, not renderUrl. It's the same file - ZapCap just names it differently depending on whether it notified you or you asked.
That's the whole loop: upload, task, receive. For the full list of fields and options, see the ZapCap API reference.
Choosing Your Output Format
The right format depends on what happens to the clip next. ZapCap returns three.
Burned-in MP4. Captions are drawn into the frames and cannot be switched off. This is the format for TikTok, Reels, Shorts, and paid ad creative, where the words have to travel with the video everywhere it goes. The subtitle API returns this ready to post.
Transparent overlay. An alpha-channel caption layer (MOV ProRes 4444 or WebM VP9 alpha) with no background behind the text. Drop it over your footage in an editor and the captions float on top of whatever sits underneath. This is the path for transparent subtitle overlays in NLE timelines.
Green-screen layer. The caption layer renders on a solid #04F404 backdrop. For editors and live tools that cannot read alpha channels, you key out the green and keep the text. The green-screen subtitles workflow covers the setup.
Adding Captions in Multiple Languages
Set a language value on the task and the translation plus restyling run server-side. The harder part is rendering scripts that trip up most caption tools, and that is where a dedicated renderer earns its keep.
Chinese text has no spaces between words, so a careless renderer snaps lines mid-character. Correct Han segmentation keeps characters whole. Japanese needs kinsoku rules so a line never opens or closes on a forbidden character. Thai runs words together with no spaces and depends on dictionary word-breaking to split text at readable points. ZapCap treats these as CJK subtitle rendering and dedicated Thai subtitles, so captions stay legible in scripts that usually come back broken.
Troubleshooting Common Captioning API Issues
A handful of problems show up early. Quick fixes for each:
No webhook arriving. Confirm your recipient URL is publicly reachable and returns a 2xx. If your environment blocks inbound calls, switch to polling the task endpoint on a timer.
Wrong caption style. Template IDs are not guesses. Call GET /templates, then drop a real ID from that list into your task.
401 or auth errors. Check that the x-api-key header is present and that the key belongs to a Pro-plan account with render credits left.
Costs higher than expected on long clips. Pricing runs per rendered minute, so a long video costs more to render. Trim it down first if you only need one section.
Getting Started
Setup is short. Create an API key, point it at a hosted test clip, and run the three calls from the walkthrough above. When you want the full list of endpoints, output formats, and use cases, the video captioning API overview has it.
FAQs About Adding Captions to Video via API
Can I add captions to a video using an API?
That is exactly what ZapCap’s video captioning API is for. You hand it a video, tell it which style you want, and it sends back the rendered clip with the words already on screen. With ZapCap the integration is small: an upload call, a task call to start the render, and a webhook that tells you when the file is ready.
What is the difference between a captioning API and a transcription API?
Transcription gets you the text, usually an SRT or VTT file you then have to draw onto the video yourself. A captioning API does that last part for you and returns the finished video. If you have ever wired up Whisper and realized you still need ffmpeg to actually show the captions, that gap is the thing a captioning API closes.
How do I burn subtitles into a video using an API?
Burned-in is what most people are after. The captions become part of the frames, so the clip looks the same on TikTok, in an Instagram feed, or on a landing page, with nothing for the platform to strip out. Request the burned-in MP4 output and that is what comes back.
Does the API support languages like Chinese, Japanese, or Thai?
It does, and this is where a lot of caption tools quietly fall apart. Set the language on the task and ZapCap handles the rest. Chinese, Japanese, and Thai do not break lines the way Latin text does, so plenty of renderers split a word down the middle or lose a character. ZapCap's CJK and Thai rendering keeps the text intact.
Can I get captions as a transparent overlay instead of burned-in?
If you are dropping captions into an existing edit, yes. Ask for the transparent output and you get an alpha-channel layer (MOV ProRes 4444 or WebM VP9 alpha) with nothing behind the text. Put it on a track above your footage and the words sit on top. For editors that cannot read alpha, there is a green-screen version you key out instead.

Jessica B
Jessica is the owner of Videolize and a seasoned video editor with 11 years of experience. She shares actionable insights on ZapCap, helping creators boost engagement with AI tools.

