Translate subtitles
A subtitle file is really two things stitched together: a timing track that has to stay perfectly synced to picture, and dialogue that has to read naturally in a new language. This endpoint translates only the second part, so the captions still land on the right frame after translation instead of drifting out of sync line by line.
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.
Why subtitle files aren't just text files
Run a raw .srt through a generic text translator and it's easy to end up with a timestamp accidentally reformatted, a cue index renumbered out of order, or a line split that changes how long a caption stays on screen. None of that shows up as an error message — it shows up as a caption that appears half a second late or lingers after the actor has already moved on. translate.srt parses the cue structure first, separating timing from dialogue, so translation only ever touches what a viewer reads, never when they read it.
SRT, VTT, and what they have in common
SubRip's .srt format dates back to a DVD ripping tool from the early 2000s and became the de facto standard because of its simplicity: a cue number, a start and end timestamp, then one or more lines of text, repeated down the file. WebVTT (.vtt) grew out of that same basic idea for web video, adding support for styling and positioning cues on screen, and is now what most browsers and video players expect natively. Both formats separate timing from text in essentially the same way, which is exactly the boundary this endpoint respects — cue numbers and timestamps pass through untouched in either format, and only the spoken-line text is translated.
Line length and reading speed
Translated languages are rarely the same length as the source — Spanish dialogue commonly runs longer than the English it's translated from — and a caption that's too long to read in the time its cue allows becomes useless no matter how accurate the translation is. Text is translated with reasonable line-length conventions in mind, keeping cues readable within their existing time window rather than producing a technically correct translation nobody can finish reading before the next line appears.
Where this fits into a video pipeline
A video platform publishing in multiple languages usually generates a single source-language transcript, then needs that same file translated into every market it serves without re-timing each version by hand. Submitting the source .srt or .vtt to /translate/srt once per target language and receiving back a file with identical cue timing means the video, the audio, and the captions all stay in lockstep regardless of how many languages are layered on top. Because price is per request plus per word rather than per minute of runtime, a short product demo with sparse dialogue costs far less to translate than a dense two-hour lecture, in direct proportion to how much was actually said.
What you can do with it
Streaming platform localization
A video platform translates the .srt file for a released episode into five languages, publishing all subtitle tracks the same day without re-timing any cues.
Conference talk captions
An event organizer translates .vtt captions for recorded conference sessions so international attendees can watch with subtitles in their own language.
E-learning course subtitles
An online course translates lecture .srt files into the languages of its enrolled regions while keeping every timestamp matched to the recorded video.
Marketing video captions
A brand translates short product-demo .vtt captions for social media into each target market's language ahead of a coordinated launch.
FAQ
How do I translate subtitles with the API?
POST the .srt or .vtt file and target language to /translate/srt, keep the returned task_id, and retrieve the translated subtitle file by webhook or a signed link valid for 24 hours.
Is the subtitle translation API free?
No, there's no free tier or trial; it costs $0.003 per request plus $0.0135 per 1000 words, and a failed task is never charged.
Will the timestamps shift after translation?
No, cue numbers and timestamps are preserved exactly as in the source file; only the dialogue text is translated.
Does it support both .srt and .vtt?
Yes, both SubRip (.srt) and WebVTT (.vtt) files are supported, including VTT's styling and positioning cues, which pass through unchanged.
Will translated lines fit within the existing caption timing?
Translation is done with reasonable line-length and reading-speed conventions in mind, aiming to keep captions readable within their existing time window.
Can I translate an entire season or playlist in bulk?
Yes, submit one async request per subtitle file and per target language, and collect each result independently as it finishes.
Is this endpoint live?
Yes, /translate/srt is live and accepting requests now.
Are my subtitle files kept after translation?
No, source and translated files are deleted after the retention window and are never used for training.
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/translate/srt \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"…"}'const res = await fetch("https://api.kit.forhosting.com/translate/srt", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "…"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/translate/srt",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "…"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/translate/srt", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"…"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"…"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/translate/srt", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "…"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "translate.srt",
"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_tokens | 20000 |
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. |
422 | task_failed | The task failed after 3 retries. You are never charged for it. |