> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nunchux.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Authenticate every request with your API key in the `X-API-Key` header or `Authorization: Bearer`. Keys start with `sk-nunchux-`.
> Image generation is synchronous: the image is in the response.
> Video generation is asynchronous: submit the job, poll its task until it reaches a terminal status, then download the output promptly, because output URLs expire.

# Grok Imagine Video

> xAI's Grok Imagine Video 1.5 and 1.5 Lite for text-to-video, image-to-video and reference-to-video, with sound, through one asynchronous endpoint.

# Grok Imagine Video

Grok Imagine Video is xAI's video model family. It generates a clip from a prompt, animates a still image, or carries people, objects and voices from references into a new scene. Clips carry a soundtrack with lip-synced speech by default. One route serves every task, and the inputs you send decide which task you get. Generation is asynchronous. You submit a request, poll it until the status is terminal, then download the clip. The route mirrors xAI's own API, so the request and response shapes are the provider's.

> [!NOTE]
>
> Best for
>
> * Native 1080p. Grok Imagine Video 1.5 renders 1080p text-to-video and image-to-video.
> * Low cost per second. Grok Imagine Video 1.5 Lite is the cheapest way to draft a clip.
> * Characters that speak. Reference-to-video on 1.5 takes up to 14 reference images and up to 3 preset voices.
> * Storyboards. On 1.5, pin the first frame, the last frame and up to 4 frames in between.

## Models

| Model | Model ID | Tasks | Resolution | Duration | Reference inputs |
| - | - | - | - | - | - |
| Grok Imagine Video 1.5 | `grok-imagine-video-1.5` | t2v, i2v, r2v | 480p, 720p, 1080p | 1 to 15 s | Up to 14 images and 3 preset voices |
| Grok Imagine Video 1.5 Lite | `grok-imagine-video-1.5-lite` | t2v, i2v | 480p, 720p, 1080p | 1 to 15 s | None |

Durations are whole seconds. Lite reaches 1080p by upscaling a 720p render.

The fields you send select the task:

| Task | Send | Resolution | Models |
| - | - | - | - |
| Text-to-video | `prompt` alone | 480p, 720p, 1080p | Both |
| Image-to-video | `image`, with an optional `prompt` | 480p, 720p, 1080p | Both |
| Reference-to-video | `reference_images`, `reference_audios`, or both | 480p, 720p | 1.5 only |
| Pinned frames | `last_frame` or `keyframes`, with an optional `image` | 480p, 720p | 1.5 only |

On 1.5, `image` combined with references or pins becomes the pinned first frame.

## Endpoints

| Step | Route |
| - | - |
| Submit | `POST /v1/xai/videos/generations` |
| Poll | `GET /v1/xai/videos/{request_id}` |

## Request

Send your API key in the `X-API-Key` header. See [Authentication](https://docs.nunchux.ai/authentication.md). Set `Content-Type: application/json`. The examples also send a `User-Agent` header that names your application.

The route accepts only the fields below. A body with any other top-level field, such as xAI's `output`, `storage_options` or `user`, returns 400 before any charge.

**model** `string` *required*

The model ID: `grok-imagine-video-1.5` or `grok-imagine-video-1.5-lite`.

**prompt** `string`

The scene, up to 20,000 characters. Required on text-to-video. Optional when the body carries an image, a reference or a pinned frame. On reference-to-video, name the references by position: `<IMAGE_0>`, `<IMAGE_1>` and so on for images, `<AUDIO_0>` to `<AUDIO_2>` for voices. When `image` is also set, it takes `<IMAGE_0>` and the reference images start at `<IMAGE_1>`.

**duration** `integer` (default: 8)

The clip length in whole seconds, 1 to 15. You are billed per second of output.

**resolution** `string` (default: 480p)

The output tier: `480p`, `720p` or `1080p`. A body with references, `last_frame` or `keyframes` takes `480p` or `720p` only. A higher tier costs more per second. The tier names the frame height only roughly: a 16:9 clip at `1080p` comes out 1920x1088.

**aspect\_ratio** `string` (default: 16:9)

The output shape: `16:9`, `9:16`, `1:1`, `4:3`, `3:4`, `3:2`, `2:3`, `21:9` or `5:2`. Image-to-video ignores it and follows the image.

**generate\_audio** `boolean` (default: true)

Whether the clip carries a soundtrack. Send `false` for a silent clip. The price per second is the same either way.

**image** `object`

The image to animate, or on 1.5 the pinned first frame. Holds `url`: a public `https` URL or a base64 `data:image/...` URI. xAI's `file_id` is rejected.

**reference\_images** `object[]`

Grok Imagine Video 1.5 only. 1 to 14 images that guide the subject, clothing or set without fixing the first frame. Each item holds `url`, as on `image`.

**reference\_audios** `object[]`

Grok Imagine Video 1.5 only. 1 to 3 preset voices. Each item is `{"voice_id": "<voice>"}`, such as `eve`. Voice IDs are case-insensitive, and an unknown one returns 400 with the list of voices. Your own audio files are not accepted: an item with `url` returns 400.

**last\_frame** `object`

Grok Imagine Video 1.5 only. The image the clip ends on. Holds `url`, as on `image`.

**keyframes** `object[]`

Grok Imagine Video 1.5 only. 1 to 4 frames the clip passes through. Each item holds `image` (an object with `url`) and `timestamp_s`, a time in seconds strictly greater than 0 and less than `duration`. xAI snaps the times to a one-third-second grid, so keep two keyframes at least 1/3 s apart.

## Response

### Submit

A 200 status means that xAI accepted the request. It does not mean that the clip is ready.

**request\_id** `string` *required*

The request ID. Poll `GET /v1/xai/videos/{request_id}` with it. It works for 7 days.

### Poll

**status** `string` *required*

`pending` while the clip renders. `done` or `failed` when it ends. Poll until you read one of the two terminal values.

**video.url** `string`

The URL of the clip. Present when `status` is `done`. The URL is time-limited. Download the clip as soon as the request is done. A later poll returns a fresh URL. It is `null` when xAI's moderation filtered the clip.

**video.duration** `number`

The length of the clip in seconds, as billed.

**video.respect\_moderation** `boolean`

`false` when xAI's moderation filtered the clip. The request is then `done`, billed, and has no URL.

**error** `object`

Present when `status` is `failed`. `error.message` explains why. Read it before you resubmit.

## Example

Submit, poll, download. The submit and the poll use different paths.

```bash cURL theme={"system"}
BASE=https://api.nunchux.com

# 1. Submit
SUBMIT=$(curl -sS --fail-with-body "$BASE/v1/xai/videos/generations" \
  -H "X-API-Key: $NUNCHUX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "User-Agent: YourApp/1.0" \
  -d '{
    "model": "grok-imagine-video-1.5",
    "prompt": "a paper boat drifting down a rain-soaked street at night",
    "duration": 8,
    "resolution": "720p",
    "aspect_ratio": "16:9"
  }') || { echo "submit failed: $SUBMIT" >&2; exit 1; }
REQ=$(jq -er '.request_id' <<<"$SUBMIT") || { echo "no request_id in: $SUBMIT" >&2; exit 1; }

# 2. Poll until the status is terminal
DELAY=5
for _ in $(seq 1 90); do
  RESP=$(curl -sS "$BASE/v1/xai/videos/$REQ" \
    -H "X-API-Key: $NUNCHUX_API_KEY" \
    -H "User-Agent: YourApp/1.0")
  STATUS=$(jq -r '.status // ""' <<<"$RESP")
  case "$STATUS" in done|failed) break ;; esac
  sleep "$DELAY"; DELAY=$(( DELAY * 2 > 30 ? 30 : DELAY * 2 ))
done

# 3. Check for failure and moderation
[ "$STATUS" = "done" ] || { echo "request $STATUS: $(jq -r '.error.message // "no error"' <<<"$RESP")" >&2; exit 1; }
URL=$(jq -r '.video.url // empty' <<<"$RESP")
[ -n "$URL" ] || { echo "filtered by moderation" >&2; exit 1; }

# 4. Download
curl -sSL -o grok.mp4 "$URL"
```

```python Python theme={"system"}
# pip install requests
import os, time, requests

BASE = "https://api.nunchux.com"
HEADERS = {
    "X-API-Key": os.environ["NUNCHUX_API_KEY"],
    "User-Agent": "YourApp/1.0",
}

# 1. Submit
submit = requests.post(
    f"{BASE}/v1/xai/videos/generations",
    headers=HEADERS,
    json={
        "model": "grok-imagine-video-1.5",
        "prompt": "a paper boat drifting down a rain-soaked street at night",
        "duration": 8,
        "resolution": "720p",
        "aspect_ratio": "16:9",
    },
)
if not submit.ok:
    raise RuntimeError(f"submit failed: HTTP {submit.status_code} {submit.text}")
req = submit.json()["request_id"]

# 2. Poll until the status is terminal
delay = 5
for _ in range(90):
    data = requests.get(f"{BASE}/v1/xai/videos/{req}", headers=HEADERS).json()
    status = data.get("status", "")
    if status in ("done", "failed"):
        break
    time.sleep(delay)
    delay = min(delay * 2, 30)
else:
    raise TimeoutError("xAI request did not reach a terminal status")

# 3. Check for failure and moderation
if status != "done":
    raise RuntimeError(f"xAI request {status}: {data.get('error')}")
url = data["video"]["url"]
if not url:
    raise RuntimeError("filtered by moderation")

# 4. Download
with requests.get(url, stream=True, timeout=300) as r:
    r.raise_for_status()
    with open("grok.mp4", "wb") as f:
        for chunk in r.iter_content(1 << 14):
            f.write(chunk)
```

The bodies below replace the submit body in the example. Image-to-video works on both models. Reference-to-video and pinned frames need Grok Imagine Video 1.5 and run at 720p or below.

```json Image-to-video theme={"system"}
{
  "model": "grok-imagine-video-1.5-lite",
  "prompt": "the camera slowly pushes in as the clouds drift past",
  "image": { "url": "https://example.com/frame.jpg" },
  "duration": 6,
  "resolution": "720p",
  "generate_audio": false
}
```

```json Reference-to-video theme={"system"}
{
  "model": "grok-imagine-video-1.5",
  "prompt": "The person from <IMAGE_0> holds up the product from <IMAGE_1> and says, in the voice of <AUDIO_0>, 'Launch day is here.'",
  "reference_images": [
    { "url": "https://example.com/person.jpg" },
    { "url": "https://example.com/product.jpg" }
  ],
  "reference_audios": [{ "voice_id": "eve" }],
  "duration": 8,
  "resolution": "720p",
  "aspect_ratio": "9:16"
}
```

```json Pinned frames theme={"system"}
{
  "model": "grok-imagine-video-1.5",
  "prompt": "the camera dollies from the doorway to the window",
  "image": { "url": "https://example.com/first.jpg" },
  "keyframes": [
    { "image": { "url": "https://example.com/middle.jpg" }, "timestamp_s": 3 }
  ],
  "last_frame": { "url": "https://example.com/last.jpg" },
  "duration": 6,
  "resolution": "720p"
}
```

## Tips

* Draft on Grok Imagine Video 1.5 Lite at 480p. It costs a fraction of 1.5 per second. Move to 1.5 or 1080p once the prompt works.
* Keep the cast small on reference-to-video. Give each reference one role and a fixed place in the scene.
* Check `video.url` before you download. A clip filtered by moderation ends as `done` with no URL.
* Poll every 5 seconds at first, then back off to 30 seconds. Polls are free and never use a concurrency slot.

## Errors and limits

A 200 status on submit means that xAI accepted the request. It does not mean that the clip succeeded. A request that fails later ends with `status` set to `failed`, and `error.message` explains why. Read it before you resubmit. A clip that xAI's moderation filters ends with `status` set to `done`, `video.url` set to `null` and `video.respect_moderation` set to `false`. It is billed.

A failed submit returns an HTTP error status. See [Error codes](https://docs.nunchux.ai/errors.md). These request shapes are rejected with 400 before any charge:

| Code | Cause |
| - | - |
| `invalid_duration` | The duration is not a whole number from 1 to 15. |
| `invalid_resolution` | The resolution is not offered, or is `1080p` on a body with references, `last_frame` or `keyframes`. |
| `invalid_prompt` | A text-to-video body without a prompt. |
| `unsupported_input` | Lite with `reference_images`, `reference_audios`, `last_frame` or `keyframes`. |
| `invalid_image` | An image is not an object with an `https` URL or a base64 `data:image/...` URI. |
| `file_id_not_supported` | An image names a `file_id` instead of a `url`. |
| `reference_audio_url_not_supported` | A voice reference carries `url` instead of `voice_id`. |
| `invalid_keyframes` | More than 4 keyframes, or a `timestamp_s` outside the clip. |
| `invalid_request` | The body carries a field this route does not accept. |

A 5xx status or a timeout on submit does not tell you whether the request was created. Never resubmit blindly. If the submit returned a `request_id`, poll it. If it returned nothing, check your credits before you try again.

xAI bills per second of output at the chosen resolution, plus a fee for each input image: `image`, `last_frame`, each keyframe and each reference image. Preset voices are free. Nunchux holds the charge at submit and settles the seconds to the length xAI reports when the clip is done. The image fee stays. A failed request is refunded. See the [pricing page](https://nunchux.ai/pricing) for the rates.

Every submit counts toward the simultaneous-jobs cap of your plan for six minutes, however fast the request finishes. A request that ends sooner does not give its slot back early, so a burst of short requests can return 429 `concurrent_limit_exceeded`. Wait and retry with backoff. Separately, at most five Grok Imagine requests per account can be in progress at once. A sixth returns 429 with the code `too_many_open_tasks`. A request stops counting toward the five when Nunchux settles it, which can be a few seconds after the poll first shows a terminal status, so a submit sent at once can still return 429. See [Rate limits](https://docs.nunchux.ai/rate-limits.md), [Credits & pricing](https://docs.nunchux.ai/credits-pricing.md) and the [Partner models overview](https://docs.nunchux.ai/partner-models/overview.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.