API / pin-first-and-last-frame-on-imagine-video

API

Pin first and last frames on Imagine video 1.5

Pin first and last frames on Imagine video 1.5

On grok-imagine-video-1.5, POST https://api.x.ai/v1/videos/generations accepts last_frame so the clip ends on an exact still. Pair image with last_frame to pin both ends; the model interpolates between them. prompt is optional when a frame is pinned. Classic grok-imagine-video rejects last_frame. Related: Generate a video from reference images, Animate an image to video.

Shape Result
image + last_frame Pinned first and last; interpolate between
last_frame only Model invents the opening; lands on the still
last_frame + reference_images / reference_audios Pinned end with reference guidance; add image to pin start too

last_frame (and image) accept a public HTTPS URL, data URI, or Files API file_id. The Python SDK and Vercel AI SDK do not yet expose a dedicated last_frame field — send it on the REST body. Resolution on this path is capped at 720p.

Interpolate between two stills

REQUEST_ID=$(curl -s -X POST https://api.x.ai/v1/videos/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $XAI_API_KEY" \
  -d '{
    "model": "grok-imagine-video-1.5",
    "prompt": "The camera dollies from the sunlit doorway to the window, settling on the closing frame.",
    "image": {"url": "<FIRST_FRAME_URL>"},
    "last_frame": {"url": "<LAST_FRAME_URL>"},
    "duration": 8,
    "aspect_ratio": "16:9",
    "resolution": "720p"
  }' | jq -r '.request_id')

while true; do
  RESULT=$(curl -s "https://api.x.ai/v1/videos/$REQUEST_ID" \
    -H "Authorization: Bearer $XAI_API_KEY")
  STATUS=$(echo "$RESULT" | jq -r '.status')
  if [ "$STATUS" = "done" ]; then
    echo "$RESULT" | jq -r '.video.url'
    break
  elif [ "$STATUS" = "failed" ] || [ "$STATUS" = "expired" ]; then
    echo "Request $STATUS"; echo "$RESULT" | jq .
    break
  fi
  sleep 5
done
import os
import time
import requests

headers = {
    "Content-Type": "application/json",
    "Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
}
r = requests.post(
    "https://api.x.ai/v1/videos/generations",
    headers=headers,
    json={
        "model": "grok-imagine-video-1.5",
        "prompt": "The camera dollies from the sunlit doorway to the window.",
        "image": {"url": "<FIRST_FRAME_URL>"},
        "last_frame": {"url": "<LAST_FRAME_URL>"},
        "duration": 8,
        "aspect_ratio": "16:9",
        "resolution": "720p",
    },
)
request_id = r.json()["request_id"]
while True:
    data = requests.get(
        f"https://api.x.ai/v1/videos/{request_id}",
        headers={"Authorization": headers["Authorization"]},
    ).json()
    if data["status"] == "done":
        print(data["video"]["url"])
        break
    if data["status"] in ("expired", "failed"):
        raise RuntimeError(data)
    time.sleep(5)

Pitfalls

  • last_frame needs grok-imagine-video-1.5. Older grok-imagine-video returns an error.
  • Reference-to-video (and first/last pin) caps at 720p even when text-to-video on 1.5 supports 1080p.
  • Output URLs expire. Download the file if you need to keep it.
  • Console API credits are a separate bill from SuperGrok's weekly pool on grok.com.