Your backend in front of Sume video: return 202 and a job id
A 30-line Node server that submits a Sume video job with POST /v1/videos and answers your browser 202 with a job id, instead of holding the request open.

Why not hold the request
Sume documents the safe shape for browsers and mobile apps: the client calls your backend, and your backend attaches the API key. The temptation is to make that backend call wait for the video. Don't. Sume's video docs say generation usually takes from 30 seconds to several minutes, every edge closes an idle request at some point, and Sume's own sync wait is capped at 30 seconds.
Do what Sume does: accept, hand back an id, let the client ask again.
The contract
| Your route | Calls Sume | Returns |
|---|---|---|
| POST /render | POST /v1/videos | 202 and your job id |
| GET /render/:id | GET /v1/videos/{id} | Sume status object (pending, in_progress, completed, failed) |
The server
Run it with node server.mjs on Node 18 or newer. It validates the id shape before forwarding, so the browser cannot make your server fetch arbitrary Sume paths, and it passes the browser's Idempotency-Key through so a double click does not bill twice.
import http from "node:http";
const H = { Authorization: "Bearer " + process.env.SUME_API_KEY, "Content-Type": "application/json" };
const send = (res, code, body) => {
res.writeHead(code, { "Content-Type": "application/json", "Cache-Control": "no-store" });
res.end(JSON.stringify(body));
};
http.createServer(async (req, res) => {
if (req.method === "POST" && req.url === "/render") {
let raw = "";
for await (const c of req) raw += c;
const { prompt } = JSON.parse(raw);
const key = req.headers["idempotency-key"];
if (!prompt || !key) return send(res, 400, { error: "prompt and Idempotency-Key required" });
const r = await fetch("https://api.sume.com/v1/videos", {
method: "POST", headers: { ...H, "Idempotency-Key": key },
body: JSON.stringify({ model: "sume/auto", prompt }) });
const j = await r.json();
return send(res, r.ok ? 202 : r.status, r.ok ? { job_id: j.id } : j);
}
const m = /^\/render\/([A-Za-z0-9_-]+)$/.exec(req.url);
if (req.method === "GET" && m) {
const r = await fetch("https://api.sume.com/v1/videos/" + m[1], { headers: H });
return send(res, r.status, await r.json());
}
send(res, 404, { error: "not found" });
}).listen(3000);Details that matter
- Check your own authorization before you forward anything; the Sume key carries your workspace spend.
- Send Cache-Control: no-store on the status route so no CDN serves a stale status.
- Return the Sume error body to the client unchanged on failures; it holds a request id safe to share with support.
- A 2xx from Sume means the job exists and paid work is in flight, not that it finished.
Sources
Related posts
More in Developers
- YouTube 'daily custom thumbnail limit reached': retry in 24 hours
YouTube caps custom thumbnails per channel per day, and the error says to retry in 24 hours. Queue Sume thumbnails and stop the loop on that message.
- YouTube Data API: 100 videos.insert calls a day, plan Sume uploads
Google's default quota gives a project 100 videos.insert calls per day. Batch Sume clips into a daily upload plan and render only what you can post.
- YouTube Data API 10,000-unit pool: budget checks for a pipeline
Google gives a project 10,000 units a day shared by endpoints other than search.list and videos.insert. Poll Sume for job status instead of spending them.
- YouTube Shorts top out at 1080p: don't render 2160 in Timeline 1.0
YouTube's help page says Shorts can be uploaded at a maximum of 1080p. Timeline 1.0 allows up to 2160 per side, so set 1080x1920 and save render time.
Written by Sume