xAI SDK and Automatic Polling
On this page
Updated November 2, 2026 based on xAI video documentation and
xai-sdk1.21.0 code: methods are calledclient.video.generateandclient.video.extend(editing usesgeneratewithvideo_url), the result exposesurl, the delay is set via atimedelta, and the default polling interval is one second.awaitrequires the asynchronous clientAsyncClient, and theVideoGenerationErrorexception imports fromxai_sdk.video. The old version called methods and an exception module that do not exist. Sources: Video Generation · Video Editing · Video Extension · xai-sdk-python.
Simplify polling with the SDK
The previous lesson had you write a polling loop by hand. It’s a useful exercise to understand the generation lifecycle, but it’s also thirty lines of code to maintain in every project, with its own timeout handling, failure cases, and status conversions. xAI’s official Python SDK (pip install xai-sdk) encapsulates all of this: you submit the request, and it handles the wait.
A single call instead of a loop
The client.video.generate method groups submission, tracking, and retrieval. It only returns control once the video is ready, or raises an exception if something went wrong.
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
# Text-to-video generation with automatic polling
video = client.video.generate(
prompt="A sunrise over the Alps, accelerated time-lapse",
model="grok-imagine-video-1.5",
duration=5,
aspect_ratio="16:9",
resolution="720p",
)
print(video.url)
print(f"Duration: {video.duration} s, model: {video.model}")
Behind these few lines, the SDK does exactly what you coded previously: submitting the request, polling the status every second by default, then returning the result or raising an exception depending on the outcome.
One setting deserves your attention right from deployment: by default, the SDK waits up to 10 minutes before giving up. That’s comfortable for a nightly production script, but far too long for a web request where a user is waiting for a response. The timeout and interval parameters, both timedelta objects, set the limit and the rhythm:
from datetime import timedelta
video = client.video.generate(
prompt="Your description",
model="grok-imagine-video-1.5",
duration=10,
timeout=timedelta(minutes=5), # 5 minutes max
interval=timedelta(seconds=5), # one check every 5 seconds
)
If you want to keep control over the wait—to relay progress, for example—client.video.start submits the request and returns its request_id, which client.video.get then queries.
Handle errors at the source
The SDK distinguishes failures, which saves you from analyzing strings to figure out what happened:
from xai_sdk.video import VideoGenerationError
try:
video = client.video.generate(
prompt="Your description",
model="grok-imagine-video-1.5",
duration=5,
)
print(f"Video ready: {video.url}")
except VideoGenerationError as e:
print(f"Generation failed [{e.code}]: {e.message}")
except TimeoutError:
print("Timeout exceeded: increase the timeout or simplify the request")
Each error calls for a different reaction, and confusing them leads to useless retry loops or, worse, billed generations that yield nothing.
| Error | Cause | Recommended action |
|---|---|---|
VideoGenerationError | Failure signaled by the API: prompt refused, invalid source, service overloaded… (code, message) | Read the code: fix the request, or retry later if the service was unavailable |
TimeoutError | Client-side wait time exceeded | Increase the timeout or reduce the duration |
RuntimeError | Deferred request expired, or completed without result | Restart the generation |
grpc.RpcError | SDK transport error, including rate limits (code RESOURCE_EXHAUSTED) and key issues | Space out requests with backoff, or check the key |
The SDK uses gRPC: an exceeded rate limit does not appear as an HTTP 429 code here, but as a grpc.RpcError with code RESOURCE_EXHAUSTED.
Editing and extension, same ease
Editing has no dedicated method: it’s generate with a source video, passed as video_url (video up to 8.7 seconds, same duration output, max 720p).
video = client.video.generate(
prompt="Transform the scene into Japanese anime style",
model="grok-imagine-video",
video_url="https://example.com/video.mp4",
)
Extension prolongs a 2 to 15 second sequence by a 2 to 10 second segment, and returns the full video:
video = client.video.extend(
prompt="Continue the scene with a pan to the right",
model="grok-imagine-video",
video_url="https://example.com/video.mp4",
duration=5,
)
Note the difference with the REST API: the SDK takes the video as a video_url parameter, whereas the JSON body expects a video object with its url property.
Never leave a video in the cloud
The SDK changes nothing about the temporary nature of result URLs. If your code just displays video.url, you paid for a generation that will soon be gone. The best practice is to chain generation and download in the same function, so it becomes impossible to forget the step.
from pathlib import Path
import httpx
async def generate_and_save(client, prompt, output_file, **options):
"""Generates a video and saves it locally (client: xai_sdk.AsyncClient)."""
video = await client.video.generate(
prompt=prompt,
model="grok-imagine-video-1.5",
**options,
)
# Immediate download
async with httpx.AsyncClient() as http:
response = await http.get(video.url)
response.raise_for_status()
Path(output_file).write_bytes(response.content)
print(f"Video saved: {output_file}")
return output_file
For storage without transit through your server, the generation storage_options parameter deposits the video in the xAI Files API, with an expiration of your choice.
Generate multiple videos at once
With the asynchronous client, each call is a coroutine, and asyncio.gather launches an entire batch in parallel. The return_exceptions=True option is decisive here: without it, a single refused request would fail the entire batch and you would lose the results already obtained.
import asyncio
import os
import xai_sdk
async def parallel_generation(requests):
"""Generates multiple videos in parallel."""
client = xai_sdk.AsyncClient(api_key=os.getenv("XAI_API_KEY"))
tasks = [
client.video.generate(
prompt=req["prompt"],
model="grok-imagine-video-1.5",
duration=req.get("duration", 5),
aspect_ratio=req.get("aspect_ratio", "16:9"),
)
for req in requests
]
results = await asyncio.gather(*tasks, return_exceptions=True)
for i, res in enumerate(results, 1):
if isinstance(res, Exception):
print(f"Request {i} failed: {res}")
else:
print(f"Request {i} completed: {res.url}")
return results
Two guardrails frame this parallelization. Submissions consume your rate limit, counted in requests per second according to your team’s tier (10 at the entry tier for video models): for a large batch, bound concurrency with an asyncio.Semaphore. Remember also what parallelism does and does not do: it does not speed up any individual generation, it only reduces the total batch time.
Should you abandon manual polling?
Not systematically. The following comparison shows where each approach takes the lead.
| Aspect | Manual polling | SDK |
|---|---|---|
| Complexity | High | Low |
| Error handling | To implement | Integrated |
| Timeout | To implement | 10 min default, configurable |
| Polling interval | To choose | 1 s default, configurable |
| Flexibility | Total | Standard (or start and get) |
| Dependency | None | xAI SDK |
Manual polling keeps its relevance in two situations: when you need fine control, for example to relay progress to a real-time interface or to integrate it into an existing queue system, and when your deployment context forbids adding an extra dependency.
Key takeaways
client.video.generatesubmits, waits, and returns the video (url,duration,model); 10-minute timeout and one-second check by default- Editing uses
generatewithvideo_url, extension usesclient.video.extend VideoGenerationError(fromxai_sdk.video),TimeoutError, andRuntimeErrordistinguish failures; rate limits arrive asgrpc.RpcError- Always download the video immediately after generation (temporary URLs)
- The asynchronous client and
asyncio.gatherparallelize a batch, within your tier’s rate limit