2026-10-12 · 4 min read
Caption videos in Node.js with fetch
Node 18 and later ship with fetch, so the caption.sh API needs no dependencies. Three calls: submit, poll, download. Here is the whole script.
The script
import { writeFile } from "node:fs/promises";
const API = "https://api.caption.sh/v1";
const headers = { Authorization: `Bearer ${process.env.CAPTION_API_KEY}` };
async function caption(videoUrl, out = "captioned.mp4") {
const submit = await fetch(`${API}/videos`, {
method: "POST",
headers: { ...headers, "Content-Type": "application/json" },
body: JSON.stringify({ source_url: videoUrl, options: { max_words: 3 } }),
});
if (!submit.ok) throw new Error(`submit failed: ${submit.status}`);
const { job_id } = await submit.json();
for (let i = 0; i < 60; i++) {
const job = await (await fetch(`${API}/jobs/${job_id}`, { headers })).json();
if (job.status === "done") break;
if (job.status === "error") throw new Error("caption job failed");
if (i === 59) throw new Error("caption job timed out");
await new Promise((r) => setTimeout(r, 5000));
}
const res = await fetch(`${API}/jobs/${job_id}/result`, { headers });
if (!res.ok) throw new Error(`download failed: ${res.status}`);
await writeFile(out, Buffer.from(await res.arrayBuffer()));
return out;
}
await caption("https://example.com/clip.mp4");Authentication
Create an API key in the caption.sh dashboard and keep it in the CAPTION_API_KEY environment variable. If this runs in a browser-facing app, call it from your server, never from client code, so the key stays private.
Things worth knowing
- The video has to be reachable by URL. For a local file, use the upload flow: the submit call returns a signed upload URL, you PUT the bytes to it, and the job starts when the upload finishes.
- A 402 on the submit means the prepaid balance is empty and a 422 means the input was rejected. Neither is worth retrying, so the script throws.
- To caption many clips, submit them all with Promise.all first and then poll each job.
When this is the wrong tool
caption.sh burns captions into the video. It does not export SRT or VTT sidecar files. If you need a transcript file, produce it with a transcription library and use caption.sh for the version people watch on a feed.
Try it
Judge the output in the playground at caption.sh first, then run the script above. The reference is at caption.sh/docs and the OpenAPI spec is at api.caption.sh/openapi.json.