Model Capabilities
Persisting Generated Output
Use storage_options on any Imagine request to persist the generated asset to your Files API storage, where you can retrieve it later through the authenticated Files API. On top of that, set storage_options.public_url to additionally generate a permanent, shareable public URL for the asset — an unauthenticated link that anyone can open and that stays alive until you revoke it.
Storage and public URL creation are independent: you can store privately without a public URL, or store with a public URL. Either way, the response also includes the ephemeral generation URL that's always returned by default.
Quick Start
Generate an image, persist it to Files, and get a shareable public URL — all in one call:
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
response = client.image.sample(
prompt="A serene Japanese garden in winter",
model="grok-imagine-image-quality",
storage_options={"filename": "garden.jpg", "public_url": True},
)
# Ephemeral, short-lived URL.
print(f"Ephemeral: {response.url}")
# Persistent file in your Files API storage.
print(f"File ID: {response.file_output.file_id}")
# Permanent, shareable public URL.
print(f"Public URL: {response.public_url}")
The response body looks like this:
JSON
{
"data": [
{
"url": "https://imgen.x.ai/xai-imgen/xai-tmp-imgen-abc123.jpg",
"file_output": {
"file_id": "file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a",
"filename": "garden.jpg",
"public_url": "https://files-cdn.x.ai/ZsqeMtdcSYWPPHTQdxXDKQ/file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a.jpg"
}
}
]
}
Key things to note:
data[i].urlis the ephemeral Imagine URL — short-lived, fine for immediate use. It's always returned, regardless of whetherstorage_optionsis specified.data[i].file_output.file_idis the stable Files API identifier for the generated asset.data[i].file_output.public_urlis the permanent public URL. Use this for sharing, embedding, and long-term storage in your app.
storage_options Reference
| Field | Type | Description |
|---|---|---|
filename | string (required) | Filename for the stored file. The extension on the public URL path is derived from this filename. |
expires_after | integer (optional) | Seconds from now until the stored file expires. Must be between 3600 (1 hour) and 2592000 (30 days). Omit for permanent storage. |
public_url | boolean or object (optional) | Set to true to create a public URL with default settings, or pass an object to configure it. Omit (or false) to store privately. |
public_url.expires_after | integer (optional) | Seconds from now until the public URL expires. Must be between 3600 (1h) and 2592000 (30d). See Expiry Behaviour. |
filename is required. Passing storage_options={"filename": "..."} with no other fields persists the asset privately: no expiry on the stored file and no public URL. You can always call create_public_url on the stored file_id later if you change your mind.
response = client.image.sample(
prompt="A red circle on a white background",
model="grok-imagine-image-quality",
storage_options={"filename": "circle.jpg"}, # store privately, no public URL
)
print(response.file_output.file_id) # file_...
print(response.file_output.filename) # circle.jpg
print(response.public_url) # None — public URL was not requested
Expiry Behaviour
storage_options exposes two independent expiry knobs: storage_options.expires_after controls when the stored file auto-deletes, and storage_options.public_url.expires_after controls when the public URL auto-revokes. Omit public_url.expires_after and the URL inherits the file's expiry (or never expires if the file has none).
A public URL can never outlive its file, and both values must be between 1 hour and 30 days. See Public URLs → Expiry Behaviour for the full rules; the examples below show how the two knobs combine on an Imagine request.
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
# Permanent file, public URL expires in 24h.
response = client.image.sample(
prompt="A futuristic city skyline at night",
model="grok-imagine-image-quality",
storage_options={"filename": "skyline.jpg", "public_url": {"expires_after": 86400}}, # 24h
)
print(response.file_output.file_id) # file_...
print(response.public_url) # https://files-cdn.x.ai/<token>/file_....jpg
print(response.file_output.public_url_expires_at) # protobuf Timestamp, ~24h from now
# File and public URL both expire in 2h (URL inherits the file's expiry).
response = client.image.sample(
prompt="A futuristic city skyline at night",
model="grok-imagine-image-quality",
storage_options={"filename": "skyline.jpg", "expires_after": 7200, "public_url": True},
)
print(response.file_output.file_id) # file_...
print(response.public_url)
print(response.file_output.expires_at) # ~2h from now
print(response.file_output.public_url_expires_at) # matches file's expires_at
# File expires in 24h, public URL expires in 1h (independent, shorter).
response = client.image.sample(
prompt="A futuristic city skyline at night",
model="grok-imagine-image-quality",
storage_options={
"filename": "skyline.jpg",
"expires_after": 86400,
"public_url": {"expires_after": 3600},
},
)
print(response.file_output.file_id) # file_...
print(response.public_url)
print(response.file_output.expires_at) # ~24h from now
print(response.file_output.public_url_expires_at) # ~1h from now (URL dies before file)
The file_output Response
Every Imagine response with storage_options set includes a file_output block on each generated asset:
| Field | Always present | Meaning |
|---|---|---|
file_id | yes | Stable Files API identifier. Use this for client.files.* operations. |
filename | yes | The filename you provided in storage_options. |
expires_at | only when the file has an expiration set | Unix timestamp when the file expires. |
public_url | only when storage_options.public_url was set and creation succeeded | The permanent shareable URL. |
public_url_expires_at | only when the public URL has an expiry | Unix timestamp when the public URL dies. Absent for permanent URLs. |
public_url_error | only on partial failure | Human-readable error when the asset was stored but public URL creation failed. See Public URL Errors. |
The Python SDK also surfaces response.public_url and response.public_url_error as top-level shortcuts on ImageResponse and VideoResponse.
Multiple Outputs (n > 1)
When you request multiple images in a single call, each image gets its own file_id and its own public_url with a unique token. The files are completely independent — revoking or deleting one does not affect the others.
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
responses = client.image.sample_batch(
prompt="A cat wearing a hat, four different art styles",
model="grok-imagine-image-quality",
n=4,
storage_options={"filename": "cat-styles.jpg", "public_url": True},
)
for r in responses:
print(r.file_output.file_id, r.public_url)
Storing Image Edit Outputs
storage_options works on /v1/images/edits the same way as /v1/images/generations. The edited result is stored as a new file.
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
response = client.image.sample(
prompt="Add a party hat to the dog",
model="grok-imagine-image-quality",
image_url="https://docs.x.ai/assets/api-examples/images/style-realistic.png",
storage_options={"filename": "party-dog.png", "public_url": True},
)
print(response.file_output.file_id) # new file, not the input
print(response.public_url)
Storing Video Outputs
The video endpoints (/v1/videos/generations, /v1/videos/edits, /v1/videos/extensions) use the same storage_options shape. Since video generation is asynchronous, file_output.public_url is populated on the completed response after the video finishes generating.
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
# SDK handles polling automatically and returns the completed video.
response = client.video.generate(
prompt="A ball bouncing slowly on a flat surface",
model="grok-imagine-video",
duration=5,
storage_options={"filename": "bouncing-ball.mp4", "public_url": True},
)
print(response.url) # ephemeral vidgen URL
print(response.file_output.file_id) # file_...
print(response.public_url) # https://files-cdn.x.ai/<token>/file_....mp4
Image-to-video, video editing, and video extension all accept storage_options the same way.
Public URL Errors
In rare cases, public URL creation can fail even when asset generation and storage succeed — for example, on a transient infrastructure issue or when the team has hit its active-URL quota. Rather than tearing down the whole request, the response still includes a valid file_output.file_id and the original asset URL, and only public_url is replaced by public_url_error:
JSON
{
"data": [
{
"url": "https://imgen.x.ai/.../xai-tmp-imgen-abc123.jpg",
"file_output": {
"file_id": "file_abc123",
"filename": "poster.jpg",
"public_url_error": "Public URL creation timed out. The file was stored successfully."
}
}
]
}
If you see public_url_error, the file is still in your storage — you can retry public URL creation directly via the Files API without regenerating the asset:
Python
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
response = client.image.sample(
prompt="A vintage poster",
model="grok-imagine-image-quality",
storage_options={"filename": "poster.jpg", "public_url": True},
)
if response.public_url_error:
# The image is stored — just retry the public URL on the file directly.
resp = client.files.create_public_url(response.file_output.file_id)
public_url = resp.public_url
else:
public_url = response.public_url
print(public_url)
Managing Files Created via Imagine
Files created through storage_options are full first-class Files API files. Use the Files API to list, retrieve, update, and delete them, and use the public URL endpoints to revoke or re-create the URL after the fact:
import os
import xai_sdk
client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
response = client.image.sample(
prompt="A futuristic city",
model="grok-imagine-image-quality",
storage_options={"filename": "city.jpg", "public_url": True},
)
file_id = response.file_output.file_id
# Inspect file metadata
file = client.files.get(file_id)
# Stop sharing publicly (keeps the file in your storage)
client.files.revoke_public_url(file_id)
# Generate a new public URL later with a different expiry
client.files.create_public_url(file_id, expires_after=604800) # 7 days
# Delete the file entirely (also tears down any active public URL)
client.files.delete(file_id)
Limitations
- Up to 1,000 active public URLs per team. Hitting the cap sets
public_url_erroron the response; revoke unused URLs to free up slots. - Expiry constraints (see Expiry Behaviour):
- Both
expires_aftervalues must be between 1 hour and 30 days. public_url.expires_aftermust be ≤ the file'sexpires_after.
- Both
- Custom filenames affect the public URL path. Passing
storage_options.filename = "my-cover.png"makes the public URL end in.png. The stored content type is still determined by the generated asset, not the filename. response_formatdoes not affect storage.storage_optionsruns whether you requesturlorb64_json.- Public URLs are independent of the ephemeral URL. Both are returned in the same response but have separate lifecycles. Revoking the public URL does not affect the ephemeral URL.
- All validation is synchronous. Invalid storage configs are rejected before generation starts, so you never waste compute on a malformed request.
Related
- Files API Integration — Overview + capstone example showing inputs and outputs together.
- Referencing Files as Input — The input side: pass stored
file_ids in place of URLs. - Files → Public URLs — Public URL lifecycle for any file, regardless of how it was created.
- Managing Files — Upload, list, retrieve, update, and delete files.
Last updated: May 26, 2026