Temporary URLs and Download Strategies
On this page
Lesson completed on November 3, 2026 based on the Image Generation and Imagine outputs pages from xAI: the
storage_optionsparameter keeps the image in the Files API and can create a permanent public URL; the documentation does not specify the lifespan of ephemeral URLs (the “403 or 404 errors” from the previous version do not appear there); images output as JPEG by default. Sources: Image Generation · Imagine outputs.
Handling Ephemeral Generated Images
URLs returned by the image generation and editing API are temporary: xAI’s documentation describes them as short-lived without specifying their lifespan, and asks you to download or process the images without delay. This constraint directly impacts how you design your applications.
The Problem with Temporary URLs
When you use response_format="url" (the default), the API returns an ephemeral URL. After an unspecified period, it stops serving the image.
# Ephemeral URL: download without delay
response = client.images.generate(
model="grok-imagine-image",
prompt="A mountain landscape"
)
url = response.data[0].url
Consequence: you cannot store these URLs in a database and reuse them later. You must systematically download the images.
Strategy 1: Immediate Download
The most reliable strategy is to download each image as soon as it is generated:
import urllib.request
import os
from datetime import datetime
def generate_and_save(prompt, output_dir="images"):
"""Generates an image and saves it immediately."""
os.makedirs(output_dir, exist_ok=True)
response = client.images.generate(
model="grok-imagine-image",
prompt=prompt
)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filepath = os.path.join(output_dir, f"gen_{timestamp}.jpg") # JPEG by default
urllib.request.urlretrieve(response.data[0].url, filepath)
print(f"Image saved: {filepath}")
return filepath
Strategy 2: Using Base64
By requesting the response in Base64, you receive the image’s binary data directly, without relying on a temporary URL:
import base64
response = client.images.generate(
model="grok-imagine-image",
prompt="A mountain landscape",
response_format="b64_json"
)
# The data is directly in the response
image_bytes = base64.b64decode(response.data[0].b64_json)
with open("mountain.jpg", "wb") as f: # JPEG by default
f.write(image_bytes)
Advantages of Base64
The advantage of Base64 comes down to one word: autonomy. The image arrives directly in the JSON response, without relying on a temporary URL that might expire before your code consumes it, and without an additional HTTP request to download it—one fewer network round trip means one fewer point of failure in your pipeline.
Disadvantages of Base64
The trade-off is mechanical: Base64 encoding swells the JSON response by about 33%, which costs you three times over—in API response transfer time, in memory consumed on the client to decode it, and in complexity if you manipulate multiple high-resolution images in the same request. For a one-off image, it’s negligible; for a batch of ten 2k images, the difference becomes tangible.
Strategy 3: Automatic Cloud Storage
For production applications, combine generation with an upload to your storage:
import boto3
import urllib.request
import tempfile
def generate_and_upload_s3(prompt, bucket, key):
"""Generates an image and uploads it directly to S3."""
response = client.images.generate(
model="grok-imagine-image",
prompt=prompt
)
# Download to a temporary file
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp:
urllib.request.urlretrieve(response.data[0].url, tmp.name)
# Upload to S3
s3 = boto3.client("s3")
s3.upload_file(tmp.name, bucket, key)
permanent_url = f"https://{bucket}.s3.amazonaws.com/{key}"
return permanent_url
Strategy 4: Letting xAI Store the Image
Every Imagine request accepts a storage_options parameter that saves the produced image in your team’s Files API, and can additionally create a permanent public URL, to share or embed. With the xAI SDK:
import os
import xai_sdk
client_xai = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))
response = client_xai.image.sample(
prompt="A mountain landscape at sunrise",
model="grok-imagine-image-2.0",
storage_options={"filename": "mountain.jpg", "public_url": True},
)
print(response.file_output.file_id) # stable identifier in the Files API
print(response.public_url) # permanent public URL
The response keeps the usual ephemeral URL and adds, for each image, a file_output block: file_id, filename, and, if requested, public_url. Only filename is required: without other options, the image is stored privately, without expiration or public URL. expires_after (from 3,600 seconds to 30 days) schedules file deletion, and public_url also accepts its own expires_after, which cannot exceed the file’s. A team can have up to 1,000 active public URLs; beyond that, or in case of an incident, the image is still stored and the response carries a public_url_error instead of the URL. Via REST, the same storage_options object goes in the request body, including for an edit.
Robust Pipeline with Retry
Temporary URLs can sometimes fail during download. Implement a retry mechanism:
import time
import urllib.request
import urllib.error
def download_with_retry(url, filepath, max_retries=3):
"""Downloads an image with retry in case of error."""
for attempt in range(max_retries):
try:
urllib.request.urlretrieve(url, filepath)
return True
except urllib.error.URLError as e:
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"Attempt {attempt + 1} failed, retrying in {wait_time}s...")
time.sleep(wait_time)
else:
print(f"Failed after {max_retries} attempts: {e}")
return False
Best Practices
All discipline stems from a single fact: URLs are temporary. Downloading must therefore follow generation immediately, in the same code flow—every minute of gap is a window where the image can become unrecoverable. For the same reason, a temporary URL must never be stored in a database as a permanent reference: it will be dead by the next lookup, and this is the kind of bug that only appears in production, days later. In critical applications where reliability comes first, bypass the problem with b64_json, which delivers the image in the response, or with storage_options, which keeps it at xAI with a stable identifier.
The download itself deserves treatment as a normal network operation: a retry with backoff to absorb transient failures, file naming by timestamp or unique identifier to avoid collisions when multiple generations run in parallel. Finally, log metadata—prompt, model, parameters—with each saved image: six months later, when you need to regenerate a visual “like that one but in 2k”, this log will be invaluable.
Key Takeaways
- Output URLs are temporary: download immediately
- The
b64_jsonformat avoids the ephemeral URL problem - Base64 increases response size by ~33%
- Implement a retry with exponential backoff for robustness
- In production, upload to permanent storage (S3, GCS), or have xAI store the image with
storage_options