
Animate a still with the Imagine API
Turn an existing still into a short clip by sending that image as the starting frame with a motion prompt on the Imagine video API. Official Image-to-Video and Video Generation document image-to-video on POST https://api.x.ai/v1/videos/generations when you set image (REST) or image_url (xAI SDK). Model id for current Video 1.5 work is grok-imagine-video-1.5, which supports native 1080p for this mode. Create a key and load credits at console.x.ai before you bill the first animation.
What you need
An XAI_API_KEY with Console balance, a public HTTPS image URL (or a base64 data URI / Files API file_id), and a prompt that describes motion and camera work from that first frame. Sending image alone is image-to-video. Combining it with last_frame, keyframes, or reference_images becomes reference-to-video with that image as a pinned first frame — stay on plain image + prompt for this job. If you have no still yet, start from text with Generate a video from text with the Imagine API.
Animate the still
- Export the key locally and keep it out of chat logs and public repos:
export XAI_API_KEY="your_api_key"
- Prefer the xAI Python SDK when you want the client to poll for you:
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
response = client.video.generate(
prompt="Make the water crash down and slowly pan out the camera",
model="grok-imagine-video-1.5",
image_url="https://docs.x.ai/assets/api-examples/video/waterfall-still.png",
duration=12,
)
print(response.url)
- Or start and poll yourself over REST:
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": "Make the water crash down and slowly pan out the camera",
"image": {"url": "https://docs.x.ai/assets/api-examples/video/waterfall-still.png"},
"duration": 12
}' | 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
- For image-to-video, the output defaults to the input image’s aspect ratio; pass
aspect_ratioonly when you intentionally want to override (and accept stretch). Pin length with Set duration on Imagine API videos and passresolution(480p,720p, or1080pongrok-imagine-video-1.5image-to-video) when the placement needs sharper output than the480pdefault. - Download the temporary URL promptly; longer durations and higher resolutions take more wall time.
After the generate
Watch the first and last frames against your source still so the subject identity held. Prompt is optional for some image-bearing modes in the docs, but a clear motion sentence usually produces a more usable clip than leaving motion to the model alone. When a finished region is wrong, edit from the output URL with Edit a video with the Imagine API. For silent output, pass generate_audio=false on the same generation call.
Pitfalls
Using a private or expiring image URL that the API cannot fetch fails the job before generation starts. Passing aspect_ratio on image-to-video when you only meant to keep the still’s frame can stretch the subject. Mixing image with reference fields without reading the mode table turns a simple animate into reference-to-video with a pinned first frame. Letting temporary video URLs expire before download loses a paid generation. Pasting keys into tickets or committing them to git forces a rotate.