2026-11-03 · 4 min read
A Caption API for Sports Highlights
A highlight clip spreads fastest right after the play, and most of those views happen with the sound off. If the commentary or context only exists in the audio, muted viewers get the picture without the story. Captions burned into the clip travel with it wherever it gets reposted.
Speed matters more than polish in this workflow, so an API fits better than an editor: submit the clipped video as soon as it is cut, poll for the render, and post the captioned version.
Submit and poll
One POST starts the render and returns a job id. Poll the job endpoint until it reports done or error, with your own timeout as the third exit.
const API = "https://api.caption.sh/v1";
const headers = {
Authorization: `Bearer ${process.env.CAPTION_API_KEY}`,
"Content-Type": "application/json",
};
async function captionClip(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");
}What to know before you batch
- Submissions are rate limited per API key. A 429 response means wait a moment and retry, it does not mean the job failed.
- Rendering is prepaid at $0.10 per minute of video. A 402 response means the balance is empty, so top up before retrying.
- Every render needs a bounded poll loop with three exits: done, error, and your own timeout. Do not poll forever.
- The API documents no webhook surface, so polling is the intended way to learn a job finished.
For a full game day, queue each highlight as it is cut instead of batching at the end. Renders run independently, so the first clips are ready to post while later ones are still processing.