Genace

Video generation API Node.js: submit once and collect the result

A Node.js video integration needs two separate operations: creating a generation and observing its job. Native fetch is sufficient; this example does not depend on a Genace SDK. Use a current Node.js runtime with native fetch and keep the API key in the process environment. The first response acknowledges work and does not contain a playable video.

1. Create the client

Save this as generate.mjs. The request timeout is longer than the server’s thirty second long poll so that a normal waiting response is not mistaken for a network failure.

const base = 'https://genace.ai/api/v1';
const key = process.env.GENACE_API_KEY;
if (!key) throw new Error('Set GENACE_API_KEY');

async function request(path, body) {
  const response = await fetch(`${base}${path}`, {
    method: body ? 'POST' : 'GET',
    headers: {
      Authorization: `Bearer ${key}`,
      ...(body ? { 'Content-Type': 'application/json' } : {}),
    },
    ...(body ? { body: JSON.stringify(body) } : {}),
    signal: AbortSignal.timeout(45000),
  });
  const data = await response.json();
  if (!response.ok) {
    throw new Error(`${response.status}: ${data.error?.code}`);
  }
  return data;
}

const accepted = await request('/video/generations', {
  model: 'seedance-2.0-fast',
  prompt: 'A paper boat drifting through a sunlit garden pond',
  duration_sec: 5,
  resolution: '720p',
});
console.log('Save this job ID:', accepted.job_id);

const deadline = Date.now() + 10 * 60 * 1000;
let result;
while (Date.now() < deadline) {
  const job = await request(`/jobs/${encodeURIComponent(accepted.job_id)}?wait=30`);
  if (['succeeded', 'failed', 'canceled'].includes(job.state)) {
    result = job;
    break;
  }
}
if (!result) throw new Error(`Still pending: ${accepted.job_id}`);
if (result.state !== 'succeeded') {
  throw new Error(`${result.state}: ${result.error?.code ?? 'no error code'}`);
}
const video = result.outputs?.find((output) => output.type === 'video');
if (!video?.url) throw new Error('Successful job has no video URL');
console.log(video.url);

2. Preserve the submission boundary

Run the file only when you intend to create a billable generation. In a service, persist accepted.job_id before starting the polling loop. A process crash then leaves a recoverable reference. Resume with GET using that identifier rather than running the POST again. There is no documented request idempotency key in this route, so an automatic POST retry can create another generation.

The submission response uses job_id; the observation response uses id. Give these distinct names in your application to avoid accidentally replacing a stored identifier with an undefined property. Store the selected model and credits_held alongside the identifier for later support and accounting.

3. Handle the two failure layers

An HTTP error means the request itself failed. A successful status request can still report a failed generation. Checking response.ok alone therefore cannot determine whether a video exists. outputs can be null while work is pending or after a failure, so indexing it unconditionally creates a misleading application exception.

When the deadline expires, report that observation stopped. That deadline does not prove that the remote job failed, and it does not cancel the job. A worker can keep observing the saved ID later. For interactive agents, return the ID immediately and use the approach in nonblocking video tools.

Verification checklist

  • Confirm one deliberate submission produces one saved job identifier.
  • Exercise pending, failed, and canceled fixtures before trying a live render.
  • Restart the client and verify it can resume observation without submitting again.
  • Confirm the final output has video type and a nonempty URL.

Compare language choices with the Python video walkthrough, and review Seedance parameters before changing duration or resolution.

Continue with code and integration

Models covered on this page

Where these facts come from

  • Editorial keyword hypothesis; search volume not measured
  • codebase: src/app/api/v1/video/generations/route.ts
  • codebase: src/app/api/v1/jobs/[id]/route.ts
  • codebase: src/ai/jobs/http.ts