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.

4 min readSume
All posts

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.

seedance-2.5 on Sume, anchor prices, list x 1.25 (read 2026-10-07)
Server-fixed settingPrice 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

All Developers posts

Written by Sume