Grok Imagine Video 1.5 in TypeScript: first frame, poll, download

A Node 18+ script that animates one still with grok-imagine-video-1.5 on POST /v1/videos, polls the job, and saves the MP4. Fields Sume refuses are listed.

5 min readSume
All posts

To animate a still with Grok Imagine from TypeScript, POST to https://api.sume.com/v1/videos with model: "grok-imagine-video-1.5" and one frame_images entry whose frame_type is first_frame, then poll GET /v1/videos/{id} until status is completed. The script below does that with Node 18 fetch and no dependencies.

The script

Save it as grok.mjs and run SUME_API_KEY=... node grok.mjs. The image URL must be public HTTPS. The Idempotency-Key header makes a retried submit return the original job, so a network timeout does not bill twice.

const base = "https://api.sume.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.SUME_API_KEY}`,
  "Content-Type": "application/json",
};

const res = await fetch(`${base}/videos`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": "grok-demo-001" },
  body: JSON.stringify({
    model: "grok-imagine-video-1.5",
    prompt: "Slow push-in, steam rising from the cup, soft morning light",
    duration: 6,
    resolution: "720p",
    frame_images: [{
      type: "image_url",
      image_url: { url: "https://example.com/cup.jpg" },
      frame_type: "first_frame",
    }],
  }),
});
if (res.status !== 202) throw new Error(`submit ${res.status}: ${await res.text()}`);
const { id } = await res.json();

for (;;) {
  await new Promise((r) => setTimeout(r, 10_000));
  const job = await (await fetch(`${base}/videos/${id}`, { headers })).json();
  if (job.status === "failed" || job.status === "cancelled") throw new Error(job.error ?? job.status);
  if (job.status === "completed") break;
}
const mp4 = await fetch(`${base}/videos/${id}/content?index=0`, { headers });
const { writeFile } = await import("node:fs/promises");
await writeFile("cup.mp4", Buffer.from(await mp4.arrayBuffer()));
console.log("saved cup.mp4");

What the Grok row refuses

The Grok row on Sume is narrower than the model on xAI's site. The request body must leave out these fields or the API answers 400:

  • No text-only request: a start image is required.
  • No last_frame entry and no input_references.
  • No aspect_ratio: the output follows the source image's ratio.
  • No size, seed or non-empty provider.options (every video model refuses these on /v1/videos).
  • duration is 4 to 15 seconds and resolution is 480p or 720p.

Polling or a webhook

Docs suggest a moderate polling interval, around 30 seconds, because a video job can take from half a minute to several minutes. A 10-second loop is fine for a short clip, but use a longer one for batches. If you would rather not poll, send an HTTPS callback_url and verify the signature on the webhook.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume