Next.js route handler: submit a Seedance 2.5 job, keep the key private
Keep your Sume API key on the server: a Next.js route handler that submits a seedance-2.5 job and returns the job id, plus a second route that polls it.

Put the call to POST https://api.sume.com/v1/videos in a Next.js route handler, read the key from a server-side environment variable, and return only the job id to the browser. A video job takes minutes, so the page submits once and then polls a second route.
Never send SUME_API_KEY to client code. A route handler runs on the server, which is the right place for it.
Route one: submit
This handler fixes the model, duration and resolution on the server, so a visitor cannot ask for 30 seconds at 1080p ($42.65 on Sume) by editing the request. Only the prompt comes from the client.
// app/api/clip/route.ts
export async function POST(req: Request) {
const { prompt } = await req.json();
if (typeof prompt !== "string" || prompt.length === 0 || prompt.length > 500) {
return Response.json({ error: "invalid prompt" }, { status: 400 });
}
const res = await fetch("https://api.sume.com/v1/videos", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUME_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "seedance-2.5",
prompt,
duration: 6,
resolution: "480p",
aspect_ratio: "9:16",
}),
});
const job = await res.json();
if (res.status !== 202) return Response.json(job, { status: res.status });
return Response.json({ id: job.id }, { status: 202 });
}Route two: poll
A second handler, app/api/clip/[id]/route.ts, calls GET /v1/videos/{id} with the same key and returns status and, when completed, a URL for the file. Your page polls it every few seconds (video generation docs). Poll responses use the same shape as the submit response: id, polling_url, status, model.
Guardrails you need in public
Anything that spends money from a public form needs limits. Authenticate the visitor, cap jobs per user per day, and keep the resolution fixed. Billing on Sume reserves the workspace balance on submit, and a 402 insufficient_credits is returned when the balance is below the reserve, so handle that status in your UI.
| Server-fixed setting | Price per job |
|---|---|
| 4 seconds at 480p | $1.08 |
| 30 seconds at 480p | $8.07 |
| 30 seconds at 720p | $17.34 |
| 30 seconds at 1080p | $42.65 |
Next steps
Add an Idempotency-Key header derived from your own order id, so a double-click returns the original job rather than spending twice. A webhook via callback_url (HTTPS) replaces polling if you have a public endpoint (jobs and results).
Practical notes
On the client, show a progress state and poll the second route every five to ten seconds, with a maximum wait. Seedance 2.5 jobs of a few seconds at 480p are quick to price and slow to finish, so the UI should not look frozen.
If you deploy on a platform with short function time limits, never wait for the video inside the handler. Return the id at once, as above, and let the polling or a webhook do the waiting.
Sources
Related posts
More in Developers
- Nightly video batch after Sora: 6, 24, 48 or 120 jobs by Sume plan
A cron that submitted 30 Sora renders at once needs a new ceiling. Sume accepts 6 jobs on Free, 24 on Pro, 48 on Startup, 120 on Scale before queue_full.
- No-code HTTP step: submit a Wan 3.0 job and get a callback_url
Any automation tool with an HTTP request step and a webhook trigger can run wan-3.0 on Sume. The request body, the HTTPS callback_url, and what to store.
- Node 22 script: create a Sume bulk queue and poll it to the end
Dependency-free Node 22 ESM script: POST a bulk queue from items.json, back off the poll, survive 429 and 503, and exit non-zero when any item failed.
- Node: flip a video backend to Sume a day before the Veo 3.1 shutdown
Google ends three Veo 3.1 preview ids on Oct 22 and its page gives a date, not an hour. A 19-line Node switch moves traffic to Sume early, with an override.
Written by Sume