SpyBara
Go Premium

Documentation 2026-06-10 23:57 UTC to 2026-06-11 10:57 UTC

17 files changed +971 −69. View all changes and history on the product overview
2026
Mon 29 23:02 Sat 27 00:02 Wed 24 22:02 Mon 22 20:59 Fri 19 05:59 Thu 18 00:57 Wed 17 15:58 Mon 15 23:02 Sun 14 22:02 Thu 11 10:57
Details

760* And many other text-based formats760* And many other text-based formats

761 761 

762## Next Steps762## Next Steps

763 

764Now that you know how to manage files, learn how to use them in chat conversations:

files/public-urls.md +247 −0 created

Details

1#### Files & Collections

2 

3# Public URLs

4 

5Every file you upload through the [Files API](/developers/files/managing-files) lives in private storage by default — fetching it requires your API key. **Public URLs** turn a stored file into a permanent, shareable link on the xAI CDN that anyone can open — no API key required.

6 

7You stay in control after creation:

8 

9* **Revoke at any time** with a single API call to immediately invalidate the URL.

10* **Auto-expire** by setting `expires_after` (1 hour up to 30 days), or let the URL **inherit the file's own expiry** so both vanish together.

11 

12Creating a public URL doesn't modify the underlying private file, and revoking it doesn't delete the file — the two lifecycles are independent.

13 

14If you need to gate access (e.g. only logged-in users), keep the file private and serve it through your own backend using the authenticated [`GET /v1/files/{file_id}/content`](/developers/files/managing-files#getting-file-content) endpoint instead.

15 

16> [!TIP]

17>

18> Generating images or videos with the [Imagine API](/developers/model-capabilities/imagine)? You

19> can create a public URL **in the same request that produces the asset** via

20> `storage_options.public_url`. See [Imagine → Files API

21> Integration](/developers/model-capabilities/imagine/files).

22 

23## Quick Start

24 

25```pythonXAI

26import os

27from xai_sdk import Client

28 

29client = Client(api_key=os.getenv("XAI_API_KEY"))

30 

31# 1. Upload (or reference an existing) file

32file = client.files.upload("/path/to/diagram.png")

33 

34# 2. Create the public URL

35resp = client.files.create_public_url(file.id)

36 

37print(resp.public_url)

38# https://files-cdn.x.ai/<token>/file_abc123.png

39 

40# 3. When you're done sharing, revoke it

41client.files.revoke_public_url(file.id)

42```

43 

44```bash

45# 1. Upload (or reference an existing) file

46FILE_ID=$(curl -s https://api.x.ai/v1/files \\

47 -H "Authorization: Bearer $XAI_API_KEY" \\

48 -F purpose=assistants \\

49 -F file=@/path/to/diagram.png | jq -r '.id')

50 

51# 2. Create the public URL (empty JSON body uses defaults)

52curl -s -X POST "https://api.x.ai/v1/files/$FILE_ID/public-url" \\

53 -H "Authorization: Bearer $XAI_API_KEY" \\

54 -H "Content-Type: application/json" \\

55 -d '{}'

56# {"public_url":"https://files-cdn.x.ai/<token>/file_abc123.png"}

57 

58# 3. When you're done sharing, revoke

59curl -s -X POST "https://api.x.ai/v1/files/$FILE_ID/public-url/revoke" \\

60 -H "Authorization: Bearer $XAI_API_KEY"

61```

62 

63> [!WARNING]

64>

65> Public URLs can only be created for files that **already exist** in your Files API storage. You

66> cannot create a public URL during upload — upload the file first, then call `create_public_url`

67> (or use `storage_options.public_url` on an Imagine request).

68 

69## Expiry Behaviour

70 

71You can optionally set an expiry on a public URL at creation time via `expires_after` (in seconds). Once the deadline passes, the URL is automatically revoked — subsequent requests return `404` and you don't need to make a follow-up API call to clean it up. The underlying file is unaffected and remains available through the authenticated Files API.

72 

73The URL's effective expiry comes from two inputs: whether you pass `expires_after` at creation, and whether the underlying file [has its own expiration](/developers/files/managing-files#upload-with-expiration-ttl).

74 

75* **File has no expiration, `expires_after` omitted** — URL never expires. It lives until you explicitly call `revoke_public_url` or delete the underlying file.

76* **File has no expiration, `expires_after` set to `N`** — URL auto-revokes `N` seconds from now. The file itself is untouched.

77* **File has its own expiration at time `T`, `expires_after` omitted** — URL inherits the file's expiry. Both disappear at `T`.

78* **File has its own expiration at time `T`, `expires_after` set to `N`** — URL auto-revokes `N` seconds from now. `N` must be ≤ the file's remaining lifetime, otherwise the request is rejected.

79 

80`expires_after` must be between **3600 seconds (1 hour)** and **2592000 seconds (30 days)**. A public URL can never outlive its file — requesting an `expires_after` greater than the file's remaining lifetime is rejected.

81 

82```pythonXAI

83import os

84from datetime import timedelta

85from xai_sdk import Client

86 

87client = Client(api_key=os.getenv("XAI_API_KEY"))

88file = client.files.upload("/path/to/photo.png")

89 

90# 1. Indefinite: omit expires_after on a file with no expiry.

91# Must call revoke_public_url to explicitly revoke the public URL.

92resp = client.files.create_public_url(file.id)

93assert not resp.HasField("expires_at")

94 

95# 2. URL-bound: pass expires_after as int seconds or a timedelta

96resp = client.files.create_public_url(file.id, expires_after=timedelta(hours=24))

97print(f"Expires at: {resp.expires_at.seconds}")

98 

99# 3. Inherited: file has its own expiration, omit expires_after on the URL

100ttl_file = client.files.upload(

101 b"\\x89PNG\\r\\n\\x1a\\n" + b"\\x00" * 32,

102 filename="short-lived.png",

103 expires_after=timedelta(hours=2),

104)

105resp = client.files.create_public_url(ttl_file.id)

106# resp.expires_at matches the file's expires_at

107```

108 

109```bash

110# 1. Indefinite — file has no expiry.

111# Must call POST /public-url/revoke to explicitly revoke.

112curl -s -X POST "https://api.x.ai/v1/files/$FILE_ID/public-url" \\

113 -H "Authorization: Bearer $XAI_API_KEY" \\

114 -H "Content-Type: application/json" -d '{}'

115# {"public_url":"..."} <- no expires_at field

116 

117# 2. URL-bound (24h)

118curl -s -X POST "https://api.x.ai/v1/files/$FILE_ID/public-url" \\

119 -H "Authorization: Bearer $XAI_API_KEY" \\

120 -H "Content-Type: application/json" \\

121 -d '{"expires_after": 86400}'

122# {"public_url":"...","expires_at":1755600000}

123 

124# 3. Inherited: upload with file expiration, then create with no expires_after

125FILE_ID=$(curl -s https://api.x.ai/v1/files \\

126 -H "Authorization: Bearer $XAI_API_KEY" \\

127 -F expires_after=7200 \\

128 -F purpose=assistants \\

129 -F file=@/path/to/photo.png | jq -r '.id')

130 

131curl -s -X POST "https://api.x.ai/v1/files/$FILE_ID/public-url" \\

132 -H "Authorization: Bearer $XAI_API_KEY" \\

133 -H "Content-Type: application/json" -d '{}'

134# {"public_url":"...","expires_at":<matches file expiry>}

135```

136 

137## Idempotency

138 

139A file can have **at most one active public URL at a time**. Calling `create_public_url` on a file that already has one returns the existing URL without producing a new one — it's safe to call repeatedly.

140 

141If you pass a different `expires_after` on a subsequent call, the existing URL's expiry is updated in place. The token in the URL stays the same.

142 

143```pythonXAI

144import os

145from xai_sdk import Client

146 

147client = Client(api_key=os.getenv("XAI_API_KEY"))

148file_id = "file_abc123"

149 

150# First call creates the URL

151resp1 = client.files.create_public_url(file_id, expires_after=86400) # 1 day

152 

153# Second call returns the same URL, no re-upload

154resp2 = client.files.create_public_url(file_id, expires_after=86400)

155assert resp1.public_url == resp2.public_url

156 

157# Calling again with a different expires_after extends/shortens the expiry

158# while keeping the same URL

159resp3 = client.files.create_public_url(file_id, expires_after=604800) # 7 days

160assert resp1.public_url == resp3.public_url

161assert resp3.expires_at.seconds > resp1.expires_at.seconds

162```

163 

164## Revoking a Public URL

165 

166Revoking invalidates the URL and clears it from the file's metadata. The original file is untouched and continues to be accessible through authenticated endpoints.

167 

168```pythonXAI

169import os

170from xai_sdk import Client

171 

172client = Client(api_key=os.getenv("XAI_API_KEY"))

173 

174# Revoke a public URL

175resp = client.files.revoke_public_url("file_abc123")

176print(f"Revoked: {resp.revoked}") # True

177print(f"Was URL: {resp.public_url}") # the URL that just stopped working

178 

179# The file itself is still available via authenticated endpoints

180file = client.files.get("file_abc123")

181print(file.filename)

182 

183# Revoke is idempotent and safe to call on:

184# - files that never had a public URL (returns revoked=False)

185# - files whose URL was already revoked (returns revoked=False)

186# - files that have been deleted

187client.files.revoke_public_url("file_abc123") # no-op, no error

188```

189 

190```bash

191curl -s -X POST "https://api.x.ai/v1/files/file_abc123/public-url/revoke" \\

192 -H "Authorization: Bearer $XAI_API_KEY"

193# {"id":"file_abc123","revoked":true,"public_url":"https://files-cdn.x.ai/..."}

194 

195# Calling again is safe — returns revoked=false

196curl -s -X POST "https://api.x.ai/v1/files/file_abc123/public-url/revoke" \\

197 -H "Authorization: Bearer $XAI_API_KEY"

198# {"id":"file_abc123","revoked":false}

199```

200 

201**Revocation is all-or-nothing.** A file can only have one public URL at a time, so revoking breaks the link for everyone who has it. If a link leaks to the wrong party, the only remedy is to revoke and create a new URL — the new one will have a fresh token and the old URL stays permanently dead.

202 

203## Finding Files with a Public URL

204 

205`get_file` and `list_files` always return the current public URL state of a file. `public_url` and `public_url_expires_at` are populated on every file with an active public URL.

206 

207You can also use the [`filter`](/developers/rest-api-reference/files/manage) parameter on `list_files` to find files with or without an active public URL:

208 

209```pythonXAI

210import os

211from xai_sdk import Client

212 

213client = Client(api_key=os.getenv("XAI_API_KEY"))

214 

215# All files that currently have a public URL

216with_url = client.files.list(filter="public_url != null")

217for f in with_url.data:

218 print(f.id, f.filename)

219 

220# All files that do not currently have a public URL

221without_url = client.files.list(filter="public_url = null")

222```

223 

224```bash

225# URL-encode the filter

226curl -s "https://api.x.ai/v1/files?filter=public_url%20!%3D%20null" \\

227 -H "Authorization: Bearer $XAI_API_KEY"

228```

229 

230## Limitations

231 

232* **Maximum file size: 50 MiB.** Larger files remain available through the authenticated Files API but cannot be made public.

233* **Restricted content types.** Only the following are eligible:

234 * `image/png` (`.png`)

235 * `image/jpeg` (`.jpg`)

236 * `video/mp4` (`.mp4`)

237 * `application/pdf` (`.pdf`)

238* **Expiry must be between 1 hour and 30 days**, and a public URL can never outlive its file.

239* **Deleting the file revokes the public URL** automatically. You cannot keep a public URL alive after the file is deleted (manually or by expiration).

240* **One public URL per file at a time.** `create_public_url` is idempotent and returns the same URL on repeat calls. After a revoke, the next `create_public_url` issues a new token — any previously shared URL becomes permanently invalid.

241* **Up to 1,000 active public URLs per team.** Revoke URLs you no longer need before creating new ones.

242 

243## Related

244 

245* [Managing Files](/developers/files/managing-files) — Upload, list, retrieve, and delete files.

246* [Imagine → Files API Integration](/developers/model-capabilities/imagine/files) — Reference stored files as Imagine inputs, persist generated assets, and create public URLs in a single request.

247* [Files API Reference](/developers/rest-api-reference/files) — Full REST endpoint documentation.

Details

64 64 

65```bash65```bash

66# 1. Create the voice from a reference clip (max 120s).66# 1. Create the voice from a reference clip (max 120s).

67curl -X POST https://api.x.ai/v1/custom-voices \67CREATE_RESPONSE=$(curl -s -X POST https://api.x.ai/v1/custom-voices \

68 -H "Authorization: Bearer $XAI_API_KEY" \68 -H "Authorization: Bearer $XAI_API_KEY" \

69 -F "name=Friendly Narrator" \69 -F "name=Friendly Narrator" \

70 -F "language=en" \70 -F "language=en" \

71 -F "gender=female" \71 -F "gender=female" \

72 -F "tone=warm" \72 -F "tone=warm" \

73 -F "use_case=narration" \73 -F "use_case=narration" \

74 -F "file=@reference.wav;type=audio/wav"74 -F "file=@reference.wav;type=audio/wav")

75 75 

76# Response:76echo "$CREATE_RESPONSE"

77# {77# {"voice_id":"abc123xy","name":"Friendly Narrator",...}

78# "voice_id": "nlbqfwie",78 

79# "name": "Friendly Narrator",79# Extract the voice_id from the response (requires jq).

80# ...80VOICE_ID=$(echo "$CREATE_RESPONSE" | jq -r '.voice_id')

81# }

82 81 

83# 2. Use the new voice for TTS.82# 2. Use the new voice for TTS.

84curl -X POST https://api.x.ai/v1/tts \83curl -X POST https://api.x.ai/v1/tts \

85 -H "Authorization: Bearer $XAI_API_KEY" \84 -H "Authorization: Bearer $XAI_API_KEY" \

86 -H "Content-Type: application/json" \85 -H "Content-Type: application/json" \

87 -d '{86 -d "{

88 "text": "Hello! This audio was synthesized using my custom voice.",87 \"text\": \"Hello! This audio was synthesized using my custom voice.\",

89 "voice_id": "nlbqfwie",88 \"voice_id\": \"$VOICE_ID\",

90 "language": "en"89 \"language\": \"en\"

91 }' \90 }" \

92 --output hello.mp391 --output hello.mp3

93```92```

94 93 


386 }),385 }),

387 },386 },

388);387);

388if (!response.ok) throw new Error(`Update error ${response.status}: ${await response.text()}`);

389console.log(await response.json());389console.log(await response.json());

390```390```

391 391 


460 460 

461Pass the custom voice as the `voice` query parameter when opening the connection. See [Text to Speech - Streaming](/developers/model-capabilities/audio/text-to-speech#streaming-tts-websocket) for the full event protocol.461Pass the custom voice as the `voice` query parameter when opening the connection. See [Text to Speech - Streaming](/developers/model-capabilities/audio/text-to-speech#streaming-tts-websocket) for the full event protocol.

462 462 

463**Prerequisite:** Install the WebSocket client library — `pip install websockets` (Python) or `npm install ws` (Node.js).

464 

463```python customLanguage="pythonWithoutSDK"465```python customLanguage="pythonWithoutSDK"

464import asyncio466import asyncio

465import base64467import base64

Details

153Clone any voice from a short reference clip with the [Custom Voices API](/developers/model-capabilities/audio/custom-voices), or create one for free in the [console](https://console.x.ai/team/default/voice/voice-library?campaign=voice-docs-tts). To find your custom voice ID in the console, click the three-dot menu on the voice card and select **Copy Voice ID**. Then pass it as `voice_id`:153Clone any voice from a short reference clip with the [Custom Voices API](/developers/model-capabilities/audio/custom-voices), or create one for free in the [console](https://console.x.ai/team/default/voice/voice-library?campaign=voice-docs-tts). To find your custom voice ID in the console, click the three-dot menu on the voice card and select **Copy Voice ID**. Then pass it as `voice_id`:

154 154 

155```bash155```bash

156# Replace YOUR_VOICE_ID with your custom voice ID from the console

157# or the GET /v1/custom-voices endpoint.

156curl -X POST https://api.x.ai/v1/tts \158curl -X POST https://api.x.ai/v1/tts \

157 -H "Authorization: Bearer $XAI_API_KEY" \159 -H "Authorization: Bearer $XAI_API_KEY" \

158 -H "Content-Type: application/json" \160 -H "Content-Type: application/json" \

159 -d '{161 -d '{

160 "text": "Hello! This is my custom voice.",162 "text": "Hello! This is my custom voice.",

161 "voice_id": "nlbqfwie",163 "voice_id": "YOUR_VOICE_ID",

162 "language": "en"164 "language": "en"

163 }' \165 }' \

164 --output hello.mp3166 --output hello.mp3


176 },178 },

177 json={179 json={

178 "text": "Hello! This is my custom voice.",180 "text": "Hello! This is my custom voice.",

179 "voice_id": "nlbqfwie", # your custom voice ID181 "voice_id": "YOUR_VOICE_ID", # replace with your custom voice ID

180 "language": "en",182 "language": "en",

181 },183 },

182)184)

185response.raise_for_status()

183with open("hello.mp3", "wb") as f:186with open("hello.mp3", "wb") as f:

184 f.write(response.content)187 f.write(response.content)

185```188```


195 },198 },

196 body: JSON.stringify({199 body: JSON.stringify({

197 text: "Hello! This is my custom voice.",200 text: "Hello! This is my custom voice.",

198 voice_id: "nlbqfwie", // your custom voice ID201 voice_id: "YOUR_VOICE_ID", // replace with your custom voice ID

199 language: "en",202 language: "en",

200 }),203 }),

201});204});

205if (!response.ok) throw new Error(`TTS error ${response.status}: ${await response.text()}`);

202fs.writeFileSync("hello.mp3", Buffer.from(await response.arrayBuffer()));206fs.writeFileSync("hello.mp3", Buffer.from(await response.arrayBuffer()));

203```207```

204 208 


808| `200` | Success | Audio bytes in the response body |812| `200` | Success | Audio bytes in the response body |

809| `400` | Bad request | Check: text is non-empty, under 15,000 chars; codec and sample rate are valid |813| `400` | Bad request | Check: text is non-empty, under 15,000 chars; codec and sample rate are valid |

810| `401` | Unauthorized | API key is missing or invalid |814| `401` | Unauthorized | API key is missing or invalid |

815| `404` | Not found | Unknown `voice_id` — verify via `GET /v1/tts/voices` (built-in) or `GET /v1/custom-voices` (custom) |

811| `429` | Rate limited | Back off and retry with exponential delay |816| `429` | Rate limited | Back off and retry with exponential delay |

812| `503` | Service unavailable | TTS service is temporarily unavailable - retry |817| `503` | Service unavailable | TTS service is temporarily unavailable - retry |

813| `500` | Server error | Retry with exponential backoff |818| `500` | Server error | Retry with exponential backoff |

Details

68 68 

69* A **public URL** pointing to an image69* A **public URL** pointing to an image

70* A **base64-encoded data URI** (e.g., `data:image/jpeg;base64,...`)70* A **base64-encoded data URI** (e.g., `data:image/jpeg;base64,...`)

71* A **`file_id`** from the [Files API](/developers/files) — see [Imagine → Files API Integration](/developers/model-capabilities/imagine/files/inputs)

71 72 

72## Multi-turn editing73## Multi-turn editing

73 74 

Details

4 4 

5Use up to three source images for a single image edit. You can specify images in the order they are sent in the request. By default, the output aspect ratio follows the first input image. You can override this by setting the `aspect_ratio` parameter to a specific ratio, such as `"1:1"` or `"16:9"`.5Use up to three source images for a single image edit. You can specify images in the order they are sent in the request. By default, the output aspect ratio follows the first input image. You can override this by setting the `aspect_ratio` parameter to a specific ratio, such as `"1:1"` or `"16:9"`.

6 6 

7Each source image can be a public URL, a base64-encoded data URI, or a `file_id` from the [Files API](/developers/files) — and you can mix kinds within a single request. See [Imagine → Files API Integration](/developers/model-capabilities/imagine/files/inputs) for `file_id` details and examples.

8 

7## Related9## Related

8 10 

9* [Image Generation](/developers/model-capabilities/images/generation) — Generate images from text prompts11* [Image Generation](/developers/model-capabilities/images/generation) — Generate images from text prompts

Details

8 8 

9Image generation uses flat per-image pricing regardless of prompt length. Each generated image incurs a fixed fee. Image edits are billed for both the input image and the generated output image. Video generation uses per-second pricing where both duration and resolution affect the total cost. For full pricing details, see the [models page](/developers/models#imagine-pricing).9Image generation uses flat per-image pricing regardless of prompt length. Each generated image incurs a fixed fee. Image edits are billed for both the input image and the generated output image. Video generation uses per-second pricing where both duration and resolution affect the total cost. For full pricing details, see the [models page](/developers/models#imagine-pricing).

10 10 

11## Image Editing11## Image Generation

12 12 

13Edit a source image with natural language. Provide a public image URL or base64-encoded data URI, then describe the change you want Grok Imagine to apply. Multi-image editing supports up to 3 source images in a single request for combining subjects, transferring styles, and composing scenes.13Generate new images from text prompts with Grok Imagine models. Configure output count (up to 10 images per request), aspect ratio, resolution, and response format.

14 14 

15```python customLanguage="pythonXAI"15```python customLanguage="pythonXAI"

16import base64

17import xai_sdk16import xai_sdk

18 17 

19client = xai_sdk.Client()18client = xai_sdk.Client()

20 19 

21# Load image from file and encode as base64

22with open("photo.png", "rb") as f:

23 image_data = base64.b64encode(f.read()).decode("utf-8")

24 

25response = client.image.sample(20response = client.image.sample(

26 prompt="Render this as a pencil sketch with detailed shading",21 prompt="A collage of London landmarks in a stenciled street‑art style",

27 model="grok-imagine-image-quality",22 model="grok-imagine-image-quality",

28 image_url=f"data:image/png;base64,{image_data}",

29)23)

30 24 

31print(response.url)25print(response.url)

32```26```

33 27 

34```bash28```bash

35# Using a public URL as the source image29curl -X POST https://api.x.ai/v1/images/generations \

36curl -X POST https://api.x.ai/v1/images/edits \

37 -H "Content-Type: application/json" \30 -H "Content-Type: application/json" \

38 -H "Authorization: Bearer $XAI_API_KEY" \31 -H "Authorization: Bearer $XAI_API_KEY" \

39 -d '{32 -d '{

40 "model": "grok-imagine-image-quality",33 "model": "grok-imagine-image-quality",

41 "prompt": "Render this as a pencil sketch with detailed shading",34 "prompt": "A collage of London landmarks in a stenciled street‑art style"

42 "image": {

43 "url": "https://docs.x.ai/assets/api-examples/images/style-realistic.png",

44 "type": "image_url"

45 }

46 }'35 }'

47```36```

48 37 

38```python customLanguage="pythonOpenAISDK"

39from openai import OpenAI

40 

41client = OpenAI(

42 base_url="https://api.x.ai/v1",

43 api_key="YOUR_API_KEY",

44)

45 

46response = client.images.generate(

47 model="grok-imagine-image-quality",

48 prompt="A collage of London landmarks in a stenciled street‑art style",

49)

50 

51print(response.data[0].url)

52```

53 

49```javascript customLanguage="javascriptAISDK"54```javascript customLanguage="javascriptAISDK"

50import { xai } from "@ai-sdk/xai";55import { xai } from "@ai-sdk/xai";

51import { generateImage } from "ai";56import { generateImage } from "ai";

52import fs from "fs";

53 

54// Load image and encode as base64

55const imageBuffer = fs.readFileSync("photo.png");

56const base64Image = imageBuffer.toString("base64");

57 57 

58const { image } = await generateImage({58const { image } = await generateImage({

59 model: xai.image("grok-imagine-image-quality"),59 model: xai.image("grok-imagine-image-quality"),

60 prompt: {60 prompt: "A collage of London landmarks in a stenciled street‑art style",

61 text: "Render this as a pencil sketch with detailed shading",

62 images: [`data:image/png;base64,${base64Image}`],

63 },

64});61});

65 62 

66console.log(image.base64);63console.log(image.base64);

67```64```

68 65 

69## Image Generation66## Image Editing

70 67 

71Generate new images from text prompts with Grok Imagine models. Configure output count (up to 10 images per request), aspect ratio, resolution, and response format.68Edit a source image with natural language. Provide a public image URL or base64-encoded data URI, then describe the change you want Grok Imagine to apply. Multi-image editing supports up to 3 source images in a single request for combining subjects, transferring styles, and composing scenes.

72 69 

73```python customLanguage="pythonXAI"70```python customLanguage="pythonXAI"

71import base64

74import xai_sdk72import xai_sdk

75 73 

76client = xai_sdk.Client()74client = xai_sdk.Client()

77 75 

76# Load image from file and encode as base64

77with open("photo.png", "rb") as f:

78 image_data = base64.b64encode(f.read()).decode("utf-8")

79 

78response = client.image.sample(80response = client.image.sample(

79 prompt="A collage of London landmarks in a stenciled street‑art style",81 prompt="Render this as a pencil sketch with detailed shading",

80 model="grok-imagine-image-quality",82 model="grok-imagine-image-quality",

83 image_url=f"data:image/png;base64,{image_data}",

81)84)

82 85 

83print(response.url)86print(response.url)

84```87```

85 88 

86```bash89```bash

87curl -X POST https://api.x.ai/v1/images/generations \90# Using a public URL as the source image

91curl -X POST https://api.x.ai/v1/images/edits \

88 -H "Content-Type: application/json" \92 -H "Content-Type: application/json" \

89 -H "Authorization: Bearer $XAI_API_KEY" \93 -H "Authorization: Bearer $XAI_API_KEY" \

90 -d '{94 -d '{

91 "model": "grok-imagine-image-quality",95 "model": "grok-imagine-image-quality",

92 "prompt": "A collage of London landmarks in a stenciled street‑art style"96 "prompt": "Render this as a pencil sketch with detailed shading",

97 "image": {

98 "url": "https://docs.x.ai/assets/api-examples/images/style-realistic.png",

99 "type": "image_url"

100 }

93 }'101 }'

94```102```

95 103 

96```python customLanguage="pythonOpenAISDK"

97from openai import OpenAI

98 

99client = OpenAI(

100 base_url="https://api.x.ai/v1",

101 api_key="YOUR_API_KEY",

102)

103 

104response = client.images.generate(

105 model="grok-imagine-image-quality",

106 prompt="A collage of London landmarks in a stenciled street‑art style",

107)

108 

109print(response.data[0].url)

110```

111 

112```javascript customLanguage="javascriptAISDK"104```javascript customLanguage="javascriptAISDK"

113import { xai } from "@ai-sdk/xai";105import { xai } from "@ai-sdk/xai";

114import { generateImage } from "ai";106import { generateImage } from "ai";

107import fs from "fs";

108 

109// Load image and encode as base64

110const imageBuffer = fs.readFileSync("photo.png");

111const base64Image = imageBuffer.toString("base64");

115 112 

116const { image } = await generateImage({113const { image } = await generateImage({

117 model: xai.image("grok-imagine-image-quality"),114 model: xai.image("grok-imagine-image-quality"),

118 prompt: "A collage of London landmarks in a stenciled street‑art style",115 prompt: {

116 text: "Render this as a pencil sketch with detailed shading",

117 images: [`data:image/png;base64,${base64Image}`],

118 },

119});119});

120 120 

121console.log(image.base64);121console.log(image.base64);


195* **[Video Editing](/developers/model-capabilities/video/editing)** — Modify an existing video with a text prompt while preserving the rest of the scene.195* **[Video Editing](/developers/model-capabilities/video/editing)** — Modify an existing video with a text prompt while preserving the rest of the scene.

196* **[Reference-to-Video](/developers/model-capabilities/video/reference-to-video)** — Guide a generated video with one or more reference images that influence the output without forcing the first frame.196* **[Reference-to-Video](/developers/model-capabilities/video/reference-to-video)** — Guide a generated video with one or more reference images that influence the output without forcing the first frame.

197* **[Video Extension](/developers/model-capabilities/video/extension)** — Continue an existing video from its last frame, combining the original and extension into one clip.197* **[Video Extension](/developers/model-capabilities/video/extension)** — Continue an existing video from its last frame, combining the original and extension into one clip.

198* **[Files API Integration](/developers/model-capabilities/imagine/files)** — Reference stored files as Imagine inputs by ID, persist generated assets to the Files API, and optionally create a permanent shareable public URL — all in a single request.

198 199 

199## Enterprise Compliance & Security200## Enterprise Compliance & Security

200 201 

Details

1#### Model Capabilities

2 

3# Files API Integration

4 

5The Imagine API integrates with the [Files API](/developers/files) in two directions:

6 

7* **Inputs** — anywhere an Imagine endpoint accepts a public URL or base64-encoded image/video, you can substitute a `file_id` from your Files storage. No need to make the file public or re-upload bytes on every call.

8* **Outputs** — Imagine can persist the generated asset to your Files storage and optionally create a permanent, shareable public URL for it, alongside the ephemeral generation URL that's always returned by default.

9 

10By default, Imagine requests return an **ephemeral** URL on `imgen.x.ai` or `vidgen.x.ai` that's only good for fetching the asset shortly after generation. The Files integration lets you both reuse stored inputs and persist outputs in the same request.

11 

12> [!TIP]

13>

14> Need to make an already uploaded file public, or want fine-grained control over create/revoke flows? See the standalone [Files → Public URLs](/developers/files/public-urls) docs.

15 

16## Putting it together: an iterative loop

17 

18The clearest payoff for using inputs and outputs together is chaining Imagine calls without ever leaving the Files API. Reference a stored `file_id` directly from a previous call's `file_output` — no need to download, re-upload, or make the file public.

19 

20```python customLanguage="pythonXAI"

21import os

22import xai_sdk

23 

24client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

25 

26# 1. Generate an image and store it

27gen = client.image.sample(

28 prompt="A futuristic city skyline at night",

29 model="grok-imagine-image-quality",

30 storage_options={"filename": "city.jpg"}, # store privately, no public URL needed

31)

32city = gen.file_output.file_id

33 

34# 2. Edit the stored image without re-uploading

35edit = client.image.sample(

36 prompt="Add neon signs to the buildings",

37 model="grok-imagine-image-quality",

38 image_file_id=city,

39 storage_options={"filename": "city-neon.jpg"},

40)

41 

42# 3. Animate the edited result into a video

43vid = client.video.generate(

44 prompt="A camera pulls back through the city",

45 model="grok-imagine-video",

46 duration=5,

47 image_file_id=edit.file_output.file_id,

48)

49 

50print(vid.url)

51```

52 

53## Related

54 

55* [Referencing Files as Input](/developers/model-capabilities/imagine/files/inputs) — Full reference + examples for the input direction.

56* [Persisting Generated Output](/developers/model-capabilities/imagine/files/outputs) — Full reference + examples for the output direction.

57* [Files → Public URLs](/developers/files/public-urls) — Public URL lifecycle for any file, regardless of how it was created.

58* [Managing Files](/developers/files/managing-files) — Upload, list, retrieve, update, and delete files.

Details

1#### Model Capabilities

2 

3# Referencing Files as Input

4 

5Anywhere an Imagine endpoint accepts a public URL or base64-encoded image/video, you can substitute a `file_id` from your [Files API](/developers/files) storage. The file is fetched server-side from your private storage, so:

6 

7* No bandwidth uploading the same image twice — useful for iterative editing loops.

8* The original file stays private (no need to make it public to use it as an input).

9* Works with both uploaded files and assets generated by earlier Imagine calls (via [`storage_options`](/developers/model-capabilities/imagine/files/outputs)).

10 

11The referenced file must be the correct content type for the endpoint (images: PNG/JPEG/WebP; videos: MP4) and must be fully uploaded.

12 

13## Editing a stored image

14 

15```python customLanguage="pythonXAI"

16import os

17import xai_sdk

18 

19client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

20 

21response = client.image.sample(

22 prompt="Add a party hat to the dog",

23 model="grok-imagine-image-quality",

24 image_file_id="file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a", # instead of image_url

25)

26 

27print(response.url)

28```

29 

30```bash

31curl -s -X POST https://api.x.ai/v1/images/edits \

32 -H "Authorization: Bearer $XAI_API_KEY" \

33 -H "Content-Type: application/json" \

34 -d '{

35 "model": "grok-imagine-image-quality",

36 "prompt": "Add a party hat to the dog",

37 "image": { "file_id": "file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a" },

38 "response_format": "url"

39 }'

40```

41 

42## Editing with multiple stored images

43 

44```python customLanguage="pythonXAI"

45response = client.image.sample(

46 prompt="Blend these two scenes into one cohesive composition",

47 model="grok-imagine-image-quality",

48 image_file_ids=[

49 "file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a",

50 "file_2cd998e7-bf12-44aa-92c8-e3d1f1c1234f",

51 ],

52)

53```

54 

55```bash

56# Each images entry independently carries url or file_id — mix kinds within a single request.

57curl -s -X POST https://api.x.ai/v1/images/edits \

58 -H "Authorization: Bearer $XAI_API_KEY" \

59 -H "Content-Type: application/json" \

60 -d '{

61 "model": "grok-imagine-image-quality",

62 "prompt": "Blend these two scenes into one cohesive composition",

63 "images": [

64 { "file_id": "file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a" },

65 { "url": "https://example.com/scene-b.jpg" }

66 ],

67 "response_format": "url"

68 }'

69```

70 

71## Image-to-video from a stored first frame

72 

73```python customLanguage="pythonXAI"

74response = client.video.generate(

75 prompt="Pan across the scene as the sky darkens",

76 model="grok-imagine-video",

77 duration=5,

78 image_file_id="file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a",

79)

80 

81print(response.url)

82```

83 

84```bash

85curl -s -X POST https://api.x.ai/v1/videos/generations \

86 -H "Authorization: Bearer $XAI_API_KEY" \

87 -H "Content-Type: application/json" \

88 -d '{

89 "model": "grok-imagine-video",

90 "prompt": "Pan across the scene as the sky darkens",

91 "duration": 5,

92 "image": { "file_id": "file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a" }

93 }'

94```

95 

96## Editing a stored video

97 

98```python customLanguage="pythonXAI"

99response = client.video.generate(

100 prompt="Add rain and a moody atmosphere",

101 model="grok-imagine-video",

102 video_file_id="file_5be118c3-da55-31dd-76e7-a1b8c8d6355b",

103)

104```

105 

106```bash

107curl -s -X POST https://api.x.ai/v1/videos/edits \

108 -H "Authorization: Bearer $XAI_API_KEY" \

109 -H "Content-Type: application/json" \

110 -d '{

111 "model": "grok-imagine-video",

112 "prompt": "Add rain and a moody atmosphere",

113 "video": { "file_id": "file_5be118c3-da55-31dd-76e7-a1b8c8d6355b" }

114 }'

115```

116 

117## Reference-to-video with multiple stored images

118 

119```python customLanguage="pythonXAI"

120response = client.video.generate(

121 prompt="A woman in this dress walks down a city street at night",

122 model="grok-imagine-video",

123 duration=5,

124 reference_image_file_ids=[

125 "file_5be118c3-da55-31dd-76e7-a1b8c8d6355b", # subject

126 "file_2cd998e7-bf12-44aa-92c8-e3d1f1c1234f", # outfit

127 ],

128)

129```

130 

131```bash

132# Each reference_images entry independently carries url or file_id — mix kinds within a single request.

133curl -s -X POST https://api.x.ai/v1/videos/generations \

134 -H "Authorization: Bearer $XAI_API_KEY" \

135 -H "Content-Type: application/json" \

136 -d '{

137 "model": "grok-imagine-video",

138 "prompt": "A woman in this dress walks down a city street at night",

139 "duration": 5,

140 "reference_images": [

141 { "file_id": "file_5be118c3-da55-31dd-76e7-a1b8c8d6355b" },

142 { "url": "https://example.com/dress.jpg" }

143 ]

144 }'

145```

146 

147## Related

148 

149* [Files API Integration](/developers/model-capabilities/imagine/files) — Overview + capstone example showing inputs and outputs together.

150* [Persisting Generated Output](/developers/model-capabilities/imagine/files/outputs) — The output side: `storage_options`, public URLs, expiry semantics.

151* [Managing Files](/developers/files/managing-files) — Upload, list, retrieve, update, and delete files.

Details

1#### Model Capabilities

2 

3# Persisting Generated Output

4 

5Use `storage_options` on any Imagine request to **persist the generated asset** to your [Files API](/developers/files) 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.

6 

7Storage 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.

8 

9## Quick Start

10 

11Generate an image, persist it to Files, and get a shareable public URL — all in one call:

12 

13```python customLanguage="pythonXAI"

14import os

15import xai_sdk

16 

17client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

18 

19response = client.image.sample(

20 prompt="A serene Japanese garden in winter",

21 model="grok-imagine-image-quality",

22 storage_options={"filename": "garden.jpg", "public_url": True},

23)

24 

25# Ephemeral, short-lived URL.

26print(f"Ephemeral: {response.url}")

27 

28# Persistent file in your Files API storage.

29print(f"File ID: {response.file_output.file_id}")

30 

31# Permanent, shareable public URL.

32print(f"Public URL: {response.public_url}")

33```

34 

35```bash

36curl -s -X POST https://api.x.ai/v1/images/generations \

37 -H "Authorization: Bearer $XAI_API_KEY" \

38 -H "Content-Type: application/json" \

39 -d '{

40 "model": "grok-imagine-image-quality",

41 "prompt": "A serene Japanese garden in winter",

42 "response_format": "url",

43 "storage_options": {

44 "filename": "garden.jpg",

45 "public_url": true

46 }

47 }'

48```

49 

50The response body looks like this:

51 

52```json

53{

54 "data": [

55 {

56 "url": "https://imgen.x.ai/xai-imgen/xai-tmp-imgen-abc123.jpg",

57 "file_output": {

58 "file_id": "file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a",

59 "filename": "garden.jpg",

60 "public_url": "https://files-cdn.x.ai/ZsqeMtdcSYWPPHTQdxXDKQ/file_7de029f4-eb66-42ee-87f8-b2a9d9e7466a.jpg"

61 }

62 }

63 ]

64}

65```

66 

67Key things to note:

68 

69* `data[i].url` is the **ephemeral** Imagine URL — short-lived, fine for immediate use. It's always returned, regardless of whether `storage_options` is specified.

70* `data[i].file_output.file_id` is the stable Files API identifier for the generated asset.

71* `data[i].file_output.public_url` is the **permanent** public URL. Use this for sharing, embedding, and long-term storage in your app.

72 

73## `storage_options` Reference

74 

75| Field | Type | Description |

76| -------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |

77| `filename` | string (**required**) | Filename for the stored file. The extension on the public URL path is derived from this filename. |

78| `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. |

79| `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. |

80| `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](#expiry-behaviour). |

81 

82`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`](/developers/files/public-urls) on the stored `file_id` later if you change your mind.

83 

84```python customLanguage="pythonXAI"

85response = client.image.sample(

86 prompt="A red circle on a white background",

87 model="grok-imagine-image-quality",

88 storage_options={"filename": "circle.jpg"}, # store privately, no public URL

89)

90 

91print(response.file_output.file_id) # file_...

92print(response.file_output.filename) # circle.jpg

93print(response.public_url) # None — public URL was not requested

94```

95 

96```bash

97curl -s -X POST https://api.x.ai/v1/images/generations \

98 -H "Authorization: Bearer $XAI_API_KEY" \

99 -H "Content-Type: application/json" \

100 -d '{

101 "model": "grok-imagine-image-quality",

102 "prompt": "A red circle on a white background",

103 "response_format": "url",

104 "storage_options": {"filename": "circle.jpg"}

105 }'

106```

107 

108## Expiry Behaviour

109 

110`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).

111 

112A public URL can never outlive its file, and both values must be between **1 hour and 30 days**. See [Public URLs → Expiry Behaviour](/developers/files/public-urls#expiry-behaviour) for the full rules; the examples below show how the two knobs combine on an Imagine request.

113 

114```python customLanguage="pythonXAI"

115import os

116import xai_sdk

117 

118client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

119 

120# Permanent file, public URL expires in 24h.

121response = client.image.sample(

122 prompt="A futuristic city skyline at night",

123 model="grok-imagine-image-quality",

124 storage_options={"filename": "skyline.jpg", "public_url": {"expires_after": 86400}}, # 24h

125)

126print(response.file_output.file_id) # file_...

127print(response.public_url) # https://files-cdn.x.ai/<token>/file_....jpg

128print(response.file_output.public_url_expires_at) # protobuf Timestamp, ~24h from now

129 

130# File and public URL both expire in 2h (URL inherits the file's expiry).

131response = client.image.sample(

132 prompt="A futuristic city skyline at night",

133 model="grok-imagine-image-quality",

134 storage_options={"filename": "skyline.jpg", "expires_after": 7200, "public_url": True},

135)

136print(response.file_output.file_id) # file_...

137print(response.public_url)

138print(response.file_output.expires_at) # ~2h from now

139print(response.file_output.public_url_expires_at) # matches file's expires_at

140 

141# File expires in 24h, public URL expires in 1h (independent, shorter).

142response = client.image.sample(

143 prompt="A futuristic city skyline at night",

144 model="grok-imagine-image-quality",

145 storage_options={

146 "filename": "skyline.jpg",

147 "expires_after": 86400,

148 "public_url": {"expires_after": 3600},

149 },

150)

151print(response.file_output.file_id) # file_...

152print(response.public_url)

153print(response.file_output.expires_at) # ~24h from now

154print(response.file_output.public_url_expires_at) # ~1h from now (URL dies before file)

155```

156 

157```bash

158# Permanent file, 24h public URL

159curl -s -X POST https://api.x.ai/v1/images/generations \

160 -H "Authorization: Bearer $XAI_API_KEY" \

161 -H "Content-Type: application/json" \

162 -d '{

163 "model": "grok-imagine-image-quality",

164 "prompt": "A futuristic city skyline at night",

165 "response_format": "url",

166 "storage_options": {

167 "filename": "skyline.jpg",

168 "public_url": {"expires_after": 86400}

169 }

170 }'

171 

172# 2h file, public URL inherits the same expiry

173curl -s -X POST https://api.x.ai/v1/images/generations \

174 -H "Authorization: Bearer $XAI_API_KEY" \

175 -H "Content-Type: application/json" \

176 -d '{

177 "model": "grok-imagine-image-quality",

178 "prompt": "A futuristic city skyline at night",

179 "response_format": "url",

180 "storage_options": {

181 "filename": "skyline.jpg",

182 "expires_after": 7200,

183 "public_url": true

184 }

185 }'

186```

187 

188## The `file_output` Response

189 

190Every Imagine response with `storage_options` set includes a `file_output` block on each generated asset:

191 

192| Field | Always present | Meaning |

193| ----------------------- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |

194| `file_id` | yes | Stable Files API identifier. Use this for `client.files.*` operations. |

195| `filename` | yes | The filename you provided in `storage_options`. |

196| `expires_at` | only when the file has an expiration set | Unix timestamp when the file expires. |

197| `public_url` | only when `storage_options.public_url` was set and creation succeeded | The permanent shareable URL. |

198| `public_url_expires_at` | only when the public URL has an expiry | Unix timestamp when the public URL dies. Absent for permanent URLs. |

199| `public_url_error` | only on partial failure | Human-readable error when the asset was stored but public URL creation failed. See [Public URL Errors](#public-url-errors). |

200 

201The Python SDK also surfaces `response.public_url` and `response.public_url_error` as top-level shortcuts on `ImageResponse` and `VideoResponse`.

202 

203## Multiple Outputs (`n > 1`)

204 

205When 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.

206 

207```python customLanguage="pythonXAI"

208import os

209import xai_sdk

210 

211client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

212 

213responses = client.image.sample_batch(

214 prompt="A cat wearing a hat, four different art styles",

215 model="grok-imagine-image-quality",

216 n=4,

217 storage_options={"filename": "cat-styles.jpg", "public_url": True},

218)

219 

220for r in responses:

221 print(r.file_output.file_id, r.public_url)

222```

223 

224```bash

225curl -s -X POST https://api.x.ai/v1/images/generations \

226 -H "Authorization: Bearer $XAI_API_KEY" \

227 -H "Content-Type: application/json" \

228 -d '{

229 "model": "grok-imagine-image-quality",

230 "prompt": "A cat wearing a hat, four different art styles",

231 "n": 4,

232 "response_format": "url",

233 "storage_options": {"filename": "cat-styles.jpg", "public_url": true}

234 }'

235```

236 

237## Storing Image Edit Outputs

238 

239`storage_options` works on `/v1/images/edits` the same way as `/v1/images/generations`. The edited result is stored as a new file.

240 

241```python customLanguage="pythonXAI"

242import os

243import xai_sdk

244 

245client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

246 

247response = client.image.sample(

248 prompt="Add a party hat to the dog",

249 model="grok-imagine-image-quality",

250 image_url="https://docs.x.ai/assets/api-examples/images/style-realistic.png",

251 storage_options={"filename": "party-dog.png", "public_url": True},

252)

253 

254print(response.file_output.file_id) # new file, not the input

255print(response.public_url)

256```

257 

258```bash

259curl -s -X POST https://api.x.ai/v1/images/edits \

260 -H "Authorization: Bearer $XAI_API_KEY" \

261 -H "Content-Type: application/json" \

262 -d '{

263 "model": "grok-imagine-image-quality",

264 "prompt": "Add a party hat to the dog",

265 "image": {

266 "url": "https://docs.x.ai/assets/api-examples/images/style-realistic.png"

267 },

268 "response_format": "url",

269 "storage_options": {"filename": "party-dog.png", "public_url": true}

270 }'

271```

272 

273## Storing Video Outputs

274 

275The 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.

276 

277```python customLanguage="pythonXAI"

278import os

279import xai_sdk

280 

281client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

282 

283# SDK handles polling automatically and returns the completed video.

284response = client.video.generate(

285 prompt="A ball bouncing slowly on a flat surface",

286 model="grok-imagine-video",

287 duration=5,

288 storage_options={"filename": "bouncing-ball.mp4", "public_url": True},

289)

290 

291print(response.url) # ephemeral vidgen URL

292print(response.file_output.file_id) # file_...

293print(response.public_url) # https://files-cdn.x.ai/<token>/file_....mp4

294```

295 

296```bash

297# Start the generation

298REQUEST_ID=$(curl -s -X POST https://api.x.ai/v1/videos/generations \

299 -H "Authorization: Bearer $XAI_API_KEY" \

300 -H "Content-Type: application/json" \

301 -d '{

302 "model": "grok-imagine-video",

303 "prompt": "A ball bouncing slowly on a flat surface",

304 "duration": 5,

305 "storage_options": {"filename": "bouncing-ball.mp4", "public_url": true}

306 }' | jq -r '.request_id')

307 

308# Poll until done

309while true; do

310 RESULT=$(curl -s "https://api.x.ai/v1/videos/$REQUEST_ID" \

311 -H "Authorization: Bearer $XAI_API_KEY")

312 STATUS=$(echo "$RESULT" | jq -r '.status')

313 if [ "$STATUS" = "done" ]; then

314 echo "$RESULT" | jq '.video.file_output'

315 break

316 fi

317 sleep 5

318done

319```

320 

321Image-to-video, video editing, and video extension all accept `storage_options` the same way.

322 

323## Public URL Errors

324 

325In 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](/developers/files/public-urls#limitations). 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`:

326 

327```json

328{

329 "data": [

330 {

331 "url": "https://imgen.x.ai/.../xai-tmp-imgen-abc123.jpg",

332 "file_output": {

333 "file_id": "file_abc123",

334 "filename": "poster.jpg",

335 "public_url_error": "Public URL creation timed out. The file was stored successfully."

336 }

337 }

338 ]

339}

340```

341 

342If you see `public_url_error`, the file is still in your storage — you can retry public URL creation directly via the [Files API](/developers/files/public-urls) without regenerating the asset:

343 

344```python customLanguage="pythonXAI"

345import os

346import xai_sdk

347 

348client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

349 

350response = client.image.sample(

351 prompt="A vintage poster",

352 model="grok-imagine-image-quality",

353 storage_options={"filename": "poster.jpg", "public_url": True},

354)

355 

356if response.public_url_error:

357 # The image is stored — just retry the public URL on the file directly.

358 resp = client.files.create_public_url(response.file_output.file_id)

359 public_url = resp.public_url

360else:

361 public_url = response.public_url

362 

363print(public_url)

364```

365 

366## Managing Files Created via Imagine

367 

368Files created through `storage_options` are full first-class Files API files. Use the [Files API](/developers/files/managing-files) to list, retrieve, update, and delete them, and use the public URL endpoints to revoke or re-create the URL after the fact:

369 

370```python customLanguage="pythonXAI"

371import os

372import xai_sdk

373 

374client = xai_sdk.Client(api_key=os.getenv("XAI_API_KEY"))

375 

376response = client.image.sample(

377 prompt="A futuristic city",

378 model="grok-imagine-image-quality",

379 storage_options={"filename": "city.jpg", "public_url": True},

380)

381file_id = response.file_output.file_id

382 

383# Inspect file metadata

384file = client.files.get(file_id)

385 

386# Stop sharing publicly (keeps the file in your storage)

387client.files.revoke_public_url(file_id)

388 

389# Generate a new public URL later with a different expiry

390client.files.create_public_url(file_id, expires_after=604800) # 7 days

391 

392# Delete the file entirely (also tears down any active public URL)

393client.files.delete(file_id)

394```

395 

396```bash

397FILE_ID="<file_output.file_id from the generation response>"

398 

399# Stop sharing publicly (file stays in your storage)

400curl -s -X POST "https://api.x.ai/v1/files/$FILE_ID/public-url/revoke" \

401 -H "Authorization: Bearer $XAI_API_KEY"

402 

403# Re-create with a 7-day expiry

404curl -s -X POST "https://api.x.ai/v1/files/$FILE_ID/public-url" \

405 -H "Authorization: Bearer $XAI_API_KEY" \

406 -H "Content-Type: application/json" \

407 -d '{"expires_after": 604800}'

408 

409# Delete the file (also revokes the public URL)

410curl -s -X DELETE "https://api.x.ai/v1/files/$FILE_ID" \

411 -H "Authorization: Bearer $XAI_API_KEY"

412```

413 

414## Limitations

415 

416* **Up to 1,000 active public URLs per team.** Hitting the cap sets `public_url_error` on the response; revoke unused URLs to free up slots.

417* **Expiry constraints** (see [Expiry Behaviour](#expiry-behaviour)):

418 * Both `expires_after` values must be between **1 hour and 30 days**.

419 * `public_url.expires_after` must be ≤ the file's `expires_after`.

420* **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.

421* **`response_format` does not affect storage.** `storage_options` runs whether you request `url` or `b64_json`.

422* **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.

423* **All validation is synchronous.** Invalid storage configs are rejected before generation starts, so you never waste compute on a malformed request.

424 

425## Related

426 

427* [Files API Integration](/developers/model-capabilities/imagine/files) — Overview + capstone example showing inputs and outputs together.

428* [Referencing Files as Input](/developers/model-capabilities/imagine/files/inputs) — The input side: pass stored `file_id`s in place of URLs.

429* [Files → Public URLs](/developers/files/public-urls) — Public URL lifecycle for any file, regardless of how it was created.

430* [Managing Files](/developers/files/managing-files) — Upload, list, retrieve, update, and delete files.

Details

4 4 

5Edit an existing video by providing a source video along with your prompt. The model understands the video content and applies your requested changes.5Edit an existing video by providing a source video along with your prompt. The model understands the video content and applies your requested changes.

6 6 

7You can provide the source video as a public URL, a base64-encoded data URI, or a `file_id` from the [Files API](/developers/files). See [Imagine → Files API Integration](/developers/model-capabilities/imagine/files/inputs) for using `file_id` inputs.

8 

7> [!WARNING]9> [!WARNING]

8 10 

9The demo below shows video editing in action. `grok-imagine-video` delivers high-fidelity edits with strong scene preservation, modifying only what you ask for while keeping the rest of the video intact:11The demo below shows video editing in action. `grok-imagine-video` delivers high-fidelity edits with strong scene preservation, modifying only what you ask for while keeping the rest of the video intact:

Details

4 4 

5Extend an existing video by providing a source video and a text prompt describing what should happen next. The result is a single video that picks up seamlessly from the last frame of the input and continues with the generated content.5Extend an existing video by providing a source video and a text prompt describing what should happen next. The result is a single video that picks up seamlessly from the last frame of the input and continues with the generated content.

6 6 

7You can provide the source video as a public URL, a base64-encoded data URI, or a `file_id` from the [Files API](/developers/files). See [Imagine → Files API Integration](/developers/model-capabilities/imagine/files/inputs) for using `file_id` inputs.

8 

7> [!WARNING]9> [!WARNING]

8 10 

9The `duration` parameter controls the length of the **extended portion only**, not the total output. For example, if your input video is 10 seconds and you set `duration` to 5, the returned video will be 15 seconds long (10s original + 5s extension).11The `duration` parameter controls the length of the **extended portion only**, not the total output. For example, if your input video is 10 seconds and you set `duration` to 5, the returned video will be 15 seconds long (10s original + 5s extension).

Details

8 8 

9* A **public URL** pointing to an image9* A **public URL** pointing to an image

10* A **base64-encoded data URI** (e.g., `data:image/jpeg;base64,...`)10* A **base64-encoded data URI** (e.g., `data:image/jpeg;base64,...`)

11* A **`file_id`** from the [Files API](/developers/files) — see [Imagine → Files API Integration](/developers/model-capabilities/imagine/files/inputs)

11 12 

12The demo below shows this in action; hold to animate a still image:13The demo below shows this in action; hold to animate a still image:

13 14 

Details

6 6 

7Unlike [image-to-video](/developers/model-capabilities/video/image-to-video), where the source image becomes the starting frame, reference images influence what appears in the video without locking in the first frame.7Unlike [image-to-video](/developers/model-capabilities/video/image-to-video), where the source image becomes the starting frame, reference images influence what appears in the video without locking in the first frame.

8 8 

9Each reference image can be provided as a public HTTPS URL or a base64-encoded data URI. In the AI SDK, set `providerOptions.xai.mode` to `"reference-to-video"` and pass the images with `providerOptions.xai.referenceImageUrls`.9Each reference image can be provided as a public HTTPS URL, a base64-encoded data URI, or a `file_id` from the [Files API](/developers/files) and you can mix kinds within a single request. See [Imagine Files API Integration](/developers/model-capabilities/imagine/files/inputs) for `file_id` details and examples.

10 

11In the Vercel AI SDK, set `providerOptions.xai.mode` to `"reference-to-video"` and pass the images with `providerOptions.xai.referenceImageUrls`.

10 12 

11> [!WARNING]13> [!WARNING]

12 14 

rate-limits.md +1 −1

Details

38| grok-4.20-0309-non-reasoning | T0: 1.8K, T1: 2.4K, T2: 3.6K, T3: 6K, T4: 10K | T0: 10M, T1: 15M, T2: 25M, T3: 45M, T4: 85M |38| grok-4.20-0309-non-reasoning | T0: 1.8K, T1: 2.4K, T2: 3.6K, T3: 6K, T4: 10K | T0: 10M, T1: 15M, T2: 25M, T3: 45M, T4: 85M |

39| grok-build-0.1 | T0: 1.8K, T1: 2.4K, T2: 3.6K, T3: 6K, T4: 10K | T0: 10M, T1: 15M, T2: 25M, T3: 45M, T4: 85M |39| grok-build-0.1 | T0: 1.8K, T1: 2.4K, T2: 3.6K, T3: 6K, T4: 10K | T0: 10M, T1: 15M, T2: 25M, T3: 45M, T4: 85M |

40| grok-4.20-multi-agent-0309 | T0: 450, T1: 600, T2: 900, T3: 1.5K, T4: 2.7K | T0: 2.5M, T1: 3.7M, T2: 6.2M, T3: 11M, T4: 21M |40| grok-4.20-multi-agent-0309 | T0: 450, T1: 600, T2: 900, T3: 1.5K, T4: 2.7K | T0: 2.5M, T1: 3.7M, T2: 6.2M, T3: 11M, T4: 21M |

41| grok-imagine-image-quality | 300 | 0 |

42| grok-imagine-image | 300 | 0 |41| grok-imagine-image | 300 | 0 |

42| grok-imagine-image-quality | 300 | 0 |

43| grok-imagine-video-1.5-preview | 60 | 0 |43| grok-imagine-video-1.5-preview | 60 | 0 |

44| grok-imagine-video | 70 | 0 |44| grok-imagine-video | 70 | 0 |

45 45 

Details

138 138 

139WebSocket endpoint: `wss://api.x.ai/v1/stt`139WebSocket endpoint: `wss://api.x.ai/v1/stt`

140 140 

141Real-time streaming speech-to-text via WebSocket. Stream raw audio as binary frames and receive JSON transcript events as the audio is processed. Configuration is done via query parameters at connection time. Each connection handles a single utterance — reconnect to transcribe another.141Real-time streaming speech-to-text via WebSocket. Stream raw audio as binary frames and receive JSON transcript events as the audio is processed. Configuration is done via query parameters at connection time.

142 142 

143Full schemas and examples: [`/stt-streaming.ws.json`](/stt-streaming.ws.json)143Full schemas and examples: [`/stt-streaming.ws.json`](/stt-streaming.ws.json)

144 144 

Details

794 794 

795WebSocket endpoint: `wss://api.x.ai/v1/stt`795WebSocket endpoint: `wss://api.x.ai/v1/stt`

796 796 

797Real-time streaming speech-to-text via WebSocket. Stream raw audio as binary frames and receive JSON transcript events as the audio is processed. Configuration is done via query parameters at connection time. Each connection handles a single utterance — reconnect to transcribe another.797Real-time streaming speech-to-text via WebSocket. Stream raw audio as binary frames and receive JSON transcript events as the audio is processed. Configuration is done via query parameters at connection time.

798 798 

799Full schemas and examples: [`/stt-streaming.ws.json`](/stt-streaming.ws.json)799Full schemas and examples: [`/stt-streaming.ws.json`](/stt-streaming.ws.json)

800 800