Burn subtitles into a video
Some captions need to be part of the picture, not a toggle a viewer might never find — this endpoint re-renders your video with subtitle text drawn directly onto each frame. It's the fix for every platform and player that quietly drops soft subtitle tracks the moment a file changes hands.
Run it online
Run this on our servers with your account. Free tools run in your browser; this one bills your KIT balance per the price above.
The problem this solves
Soft subtitles are elegant on paper — a separate file, switchable, translatable — but plenty of real-world destinations don't cooperate: short-form social apps, embedded players on marketing sites, WhatsApp forwards, in-store displays and kiosks routinely ignore or strip external subtitle tracks. Whoever owns publishing for those channels ends up needing the text physically painted onto the video, which is a rendering job, not a metadata job, and not something worth doing by hand in an editor for every export.
How the job runs
Send the source video and its subtitle cues to POST /video/burn-subtitles and you get a task_id back while the render queues. The service composites the caption text onto every relevant frame at the correct timing, re-encodes the video, and hands you back a single file where the subtitles are simply part of the image — no external track, no player support required, no risk of the captions getting lost in transit.
Why 'burning in' is the right word
The term comes from the film and broadcast world, where open captions were literally exposed onto the print during optical printing, permanent and impossible to switch off — as opposed to closed captions, decoded by the receiving device only if it chose to. Digital video inherited the same split, and burning in today does exactly what it always did: it trades flexibility for guaranteed visibility, which is precisely the trade-off social and broadcast delivery usually demand.
What you give up, deliberately
Burned-in text can't be turned off, translated after the fact, or read by a screen reader, so it's the wrong tool when accessibility toggles matter — that's what the soft subtitle generator is for. Use this endpoint specifically for the last mile: final exports headed to platforms and players you don't control, where the only guarantee you get is what's baked into the pixels.
Fitting it into a pipeline
In an automated workflow this endpoint typically sits after transcription and before final delivery: generate the subtitle cues, review or translate them if needed, then burn the chosen version in as the very last step before publishing. Because it's asynchronous like every stage in the chain, batching dozens of finished exports overnight is just a matter of looping the call. This endpoint is finishing final deployment; the request shape and pricing here are the ones that will go live, and failed renders are never billed.
What you can do with it
Short-form video for social apps
A brand exports vertical video with captions burned in because the target apps don't reliably render uploaded subtitle files, so the text has to already be in the frame.
Kiosk and in-store displays
A retailer plays product videos on unattended screens that have no subtitle-track support at all, so captions are burned in during export instead of relying on the player.
Forwarded and re-shared clips
A newsroom burns subtitles into breaking-news clips knowing the video will be re-downloaded and reshared across messaging apps that strip external subtitle metadata.
Multilingual campaign variants
A marketing team generates one language track per market and burns each into its own export, producing several ready-to-publish files from a single source video.
FAQ
What does the burn subtitles into video API actually change in the file?
It re-renders the video so the subtitle text is part of the image on every frame, unlike a soft subtitle file that a player can turn on or off.
Can I turn burned-in subtitles off later?
No — that's the nature of hardcoded captions. If you need a removable, translatable track instead, use the separate subtitle generator endpoint.
Is this API free to try?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
How do I supply the subtitle text to burn in?
You provide the subtitle cues alongside the source video in the request; the service composites that text onto the corresponding frames during the render.
How do I get the finished video back?
Through a signed webhook when the render completes, or a signed link that remains valid for 24 hours after the job finishes.
Does burning in subtitles affect video quality?
The video is re-encoded to add the text, so output settings and bitrate matter the same way they do for any re-render; the process itself doesn't degrade quality beyond that encode step.
Can I burn in subtitles for several videos at once?
Yes, since every call is an independent async task with its own task_id, processing a batch is just a matter of submitting multiple requests.
Is the endpoint live yet?
It's in final deployment; the endpoint, price and job format documented here are final and will begin accepting requests shortly.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/video/burn-subtitles \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"audio":"https://ejemplo.com/audio.mp3"}'const res = await fetch("https://api.kit.forhosting.com/video/burn-subtitles", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"audio": "https://ejemplo.com/audio.mp3"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/video/burn-subtitles",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"audio": "https://ejemplo.com/audio.mp3"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/video/burn-subtitles", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"audio":"https://ejemplo.com/audio.mp3"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"audio":"https://ejemplo.com/audio.mp3"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/video/burn-subtitles", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"audio": "https://ejemplo.com/audio.mp3"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "video.burn_subtitles",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 500 |
max_minutes | 60 |
max_megapixels | 3.9 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |