2026-11-01 · 5 min read
A Caption API for Customer Testimonial Videos
A customer saying the thing is the whole point of a testimonial video. When that video plays muted in a feed or on a landing page, the words only land if they are on the screen. Teams that publish testimonials regularly tend to caption them all, and doing that by hand in an editor is the part that does not scale.
Captioning testimonials through an API
caption.sh takes a video URL and returns the same video with styled captions burned into the pixels. The flow is asynchronous: POST to /v1/videos, receive a job_id, poll GET /v1/jobs/{job_id} until the status is done, then download the rendered MP4. It costs $0.10 per minute of video, prepaid, and failed renders are not charged.
const API = "https://api.caption.sh/v1";
const headers = {
Authorization: `Bearer ${process.env.CAPTION_API_KEY}`,
"Content-Type": "application/json",
};
async function captionTestimonial(sourceUrl) {
const res = await fetch(`${API}/videos`, {
method: "POST",
headers,
body: JSON.stringify({
source_url: sourceUrl,
options: { font: "inter" },
}),
});
if (res.status === 402) throw new Error("out of credits");
if (!res.ok) throw new Error(`submit failed: ${res.status}`);
const { job_id } = await res.json();
for (let i = 0; i < 120; i++) {
await new Promise((r) => setTimeout(r, 5000));
const job = await (
await fetch(`${API}/jobs/${job_id}`, { headers })
).json();
if (job.status === "error") throw new Error("render failed");
if (job.status === "done") return job_id;
}
throw new Error("render timed out");
}
Fitting it into a publishing flow
- Caption at intake time: when a new testimonial lands in your asset bucket, submit it and store the captioned MP4 next to the raw one, so every downstream use already has captions.
- A 429 means the transcription throughput ceiling is reached. A 429 means wait and retry, not start over; accepted jobs keep rendering.
- Results are stored for 24 hours and then deleted. Pull the file into your own storage when the job completes.
- The options object controls styling, so a brand look applies to every testimonial without opening an editor.
The win is consistency: every testimonial that goes out carries the same readable captions, whether it was shot on a phone by a happy customer or produced by an agency. If your team publishes a handful of testimonials a month, the whole caption step becomes one small script.