269 269
270`prompt` is optional in every first & last frame request. Include one to steer motion and camera work between the frames; omit it to let the frames alone drive the clip.270`prompt` is optional in every first & last frame request. Include one to steer motion and camera work between the frames; omit it to let the frames alone drive the clip.
271 271
272272`last_frame` uses the same URL, data-URI, and `file_id` shapes as [image-to-video](/developers/model-capabilities/video/image-to-video). The Python SDK and Vercel AI SDK do not yet expose a dedicated `last_frame` parameter; send it on the REST body.`last_frame` uses the same URL, data-URI, and `file_id` shapes as [image-to-video](/developers/model-capabilities/video/image-to-video). In the xAI Python SDK, pass `last_frame_url` (or `last_frame_file_id` for a Files API upload) alongside `image_url` (or `image_file_id`). The Vercel AI SDK does not expose `last_frame` yet; send it on the REST body.
273 273
274Classic `grok-imagine-video` rejects `last_frame` and rejects combining `image` with reference inputs.274Classic `grok-imagine-video` rejects `last_frame` and rejects combining `image` with reference inputs.
275 275
276```python customLanguage="pythonXAI"
277import os
278import xai_sdk
279
280client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
281
282response = client.video.generate(
283 prompt="The camera dollies from the sunlit doorway to the window, settling on the closing frame.",
284 model="grok-imagine-video-1.5",
285 image_url="<FIRST_FRAME_URL>",
286 last_frame_url="<LAST_FRAME_URL>",
287 duration=8,
288 aspect_ratio="16:9",
289 resolution="720p",
290)
291
292print(response.url)
293```
294
276```python customLanguage="pythonRequests"295```python customLanguage="pythonRequests"
277import os296import os
278import time297import time
363| Spacing | Anchors snap to a 1/3-second grid. Two keyframes that round to the same slot are rejected, so keep them at least 1/3 s apart. |382| Spacing | Anchors snap to a 1/3-second grid. Two keyframes that round to the same slot are rejected, so keep them at least 1/3 s apart. |
364| Inputs | Each `image` accepts the same URL, data-URI, and `file_id` shapes as [image-to-video](/developers/model-capabilities/video/image-to-video). |383| Inputs | Each `image` accepts the same URL, data-URI, and `file_id` shapes as [image-to-video](/developers/model-capabilities/video/image-to-video). |
365 384
366385The Python SDK and Vercel AI SDK do not yet expose a dedicated `keyframes` parameter; send it on the REST body. Classic `grok-imagine-video` rejects `keyframes`, and keyframes cannot be combined with video editing.In the xAI Python SDK, `keyframes` is a list of dicts that flatten the REST `image` object: each entry takes `image_url` (or `image_file_id`) and `timestamp`, the REST `timestamp_s` in seconds, as in `{"image_url": "<KEYFRAME_URL_1>", "timestamp": 2.0}`. Pin the endpoints with `image_url` and `last_frame_url`. The Vercel AI SDK does not expose `keyframes` yet; send it on the REST body.
367
368```python customLanguage="pythonRequests"
369import os
370import time
371import requests
372 386
373387headers = {Classic `grok-imagine-video` rejects `keyframes`, and keyframes cannot be combined with video editing.
374 "Content-Type": "application/json",
375 "Authorization": f"Bearer {os.environ['XAI_API_KEY']}",
376}
377
378response = requests.post(
379 "https://api.x.ai/v1/videos/generations",
380 headers=headers,
381 json={
382 "model": "grok-imagine-video-1.5",
383 "prompt": "A slow tracking shot through the workshop: the sketch on the bench becomes a clay model, then the finished bronze in the window.",
384 "image": {"url": "<FIRST_FRAME_URL>"},
385 "keyframes": [
386 {"image": {"url": "<KEYFRAME_URL_1>"}, "timestamp_s": 3.0},
387 {"image": {"url": "<KEYFRAME_URL_2>"}, "timestamp_s": 6.0},
388 ],
389 "last_frame": {"url": "<LAST_FRAME_URL>"},
390 "duration": 8,
391 "aspect_ratio": "16:9",
392 "resolution": "720p",
393 },
394)
395
396request_id = response.json()["request_id"]
397 388
398389while True:### Example: a year in one request
399 result = requests.get(
400 f"https://api.x.ai/v1/videos/{request_id}",
401 headers={"Authorization": headers["Authorization"]},
402 )
403 data = result.json()
404 if data["status"] == "done":
405 print(data["video"]["url"])
406 break
407 elif data["status"] == "expired":
408 print("Request expired")
409 break
410 time.sleep(5)
411```
412 390
413391```bashThis request uses every kind of pin at once. A single oak is pinned as winter in the first frame, spring and summer as keyframes at 3 and 6 seconds, and autumn as the last frame at 9 seconds; the model animates the seasons in between. Select a pin to see the exact frame the video passes through, or press play to watch the whole year.
414REQUEST_ID=$(curl -s -X POST https://api.x.ai/v1/videos/generations \
415 -H "Content-Type: application/json" \
416 -H "Authorization: Bearer $XAI_API_KEY" \
417 -d '{
418 "model": "grok-imagine-video-1.5",
419 "prompt": "A slow tracking shot through the workshop: the sketch on the bench becomes a clay model, then the finished bronze in the window.",
420 "image": {"url": "<FIRST_FRAME_URL>"},
421 "keyframes": [
422 {"image": {"url": "<KEYFRAME_URL_1>"}, "timestamp_s": 3.0},
423 {"image": {"url": "<KEYFRAME_URL_2>"}, "timestamp_s": 6.0}
424 ],
425 "last_frame": {"url": "<LAST_FRAME_URL>"},
426 "duration": 8,
427 "aspect_ratio": "16:9",
428 "resolution": "720p"
429 }' | jq -r '.request_id')
430 392
431393while true; doPins work best when they share a scene. The four stills started as one generated winter image, and each season is an [image edit](/developers/model-capabilities/images/editing) of it, so the tree, horizon, and camera angle stay identical and the model only has to animate the change of season.
432 RESULT=$(curl -s https://api.x.ai/v1/videos/$REQUEST_ID \
433 -H "Authorization: Bearer $XAI_API_KEY")
434 STATUS=$(echo "$RESULT" | jq -r '.status')
435 if [ "$STATUS" = "done" ]; then
436 echo "$RESULT" | jq -r '.video.url'
437 break
438 elif [ "$STATUS" = "failed" ] || [ "$STATUS" = "expired" ]; then
439 echo "Request $STATUS"; echo "$RESULT" | jq .
440 break
441 fi
442 sleep 5
443done
444```
445 394
446## Related395## Related
447 396