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

# Grok Official Video Models

> Generate videos from text or reference images with grok-imagine-video and grok-imagine-video-1.5, or edit a source video with the base model.

<Info>
  This page covers the official models `grok-imagine-video` and `grok-imagine-video-1.5`. They are separate from `grok-imagine-1.5-video-ext` on the existing generation page; do not mix their model names or parameters.
</Info>

<Warning>
  Never expose an API key in a browser bundle, public environment variable, LocalStorage, URL, or frontend logs. Call APIMart through your backend or BFF.
</Warning>

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.apimart.ai/v1/videos/generations \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --header 'Accept: application/json' \
    --header 'Idempotency-Key: 7d0141e4-a19a-4650-a717-dab777b3a330' \
    --data '{
      "model": "grok-imagine-video",
      "prompt": "A cinematic aerial shot of a coastal city at sunrise",
      "duration": 5,
      "resolution": "720p",
      "aspect_ratio": "16:9",
      "nsfw_check": true
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.apimart.ai/v1/videos/generations", {
    method: "POST",
    headers: {
      Authorization: "Bearer <token>",
      "Content-Type": "application/json",
      Accept: "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      model: "grok-imagine-video",
      prompt: "Improve motion consistency and apply cinematic color grading",
      video: { url: "https://cdn.example.com/source-video.mp4" },
    }),
  });

  console.log(response.status, await response.json());
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "code": 200,
    "data": [{
      "status": "submitted",
      "task_id": "task_01M09Y4Y101HTFFPV2QPW9DQ3W"
    }]
  }
  ```

  ```json 400 theme={null}
  {
    "error": {
      "message": "Invalid request parameters",
      "type": "invalid_request_error",
      "param": "resolution",
      "code": "invalid_request_error"
    }
  }
  ```
</ResponseExample>

## Integration overview

All modes use the same asynchronous endpoint:

```http theme={null}
POST https://api.apimart.ai/v1/videos/generations
```

| Request fields             | Mode                      | Models                    |
| -------------------------- | ------------------------- | ------------------------- |
| No `image_urls` or `video` | Text to video             | Both models               |
| `image_urls`               | Reference images to video | Both models               |
| `video`                    | Video editing             | `grok-imagine-video` only |

After submission, save `data[0].task_id`, then poll:

```http theme={null}
GET https://api.apimart.ai/v1/tasks/{task_id}
```

<Warning>
  Do not send `X-APIMart-Response-Version`. It switches to an HTTP `202` response schema; this page uses the legacy HTTP `200` asynchronous task response.
</Warning>

## Model capabilities

| Capability                          | `grok-imagine-video` | `grok-imagine-video-1.5` |
| ----------------------------------- | :------------------: | :----------------------: |
| Text to video                       |           ✅          |             ✅            |
| Single or multiple reference images |           ✅          |             ✅            |
| Video editing                       |           ✅          |             ❌            |
| `480p`                              |           ✅          |             ✅            |
| `720p`                              |           ✅          |             ✅            |
| `1080p`                             |           ❌          |             ✅            |
| Duration: 1–15 seconds              |         1–15         |           1–15           |
| Prompt                              |        1–8000        |          1–8000          |

```text theme={null}
duration     = 8
resolution   = 480p
aspect_ratio = auto
```

The public contract does not define a fixed reference-image count limit. Keep a non-empty array of valid URLs in their original order; do not reuse image-model limits.

## Request headers

<ParamField header="Authorization" type="string" required>
  `Bearer <APIMART_API_KEY>`
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Always use `application/json`.
</ParamField>

<ParamField header="Accept" type="string">
  `application/json`
</ParamField>

<ParamField header="Idempotency-Key" type="string">
  `Idempotency-Key` is optional and strongly recommended for paid generation and editing. It accepts 1–191 visible ASCII characters; UUID is recommended. Reuse the original key and identical body for a network retry. Do not switch keys when the result is uncertain.

  Use a new key for each new logical operation. A retry of the same operation must reuse the original key and identical body.
</ParamField>

## Request parameters

### Common fields

<ParamField body="model" type="string" required>
  Official model name; video editing supports the base model only

  * `grok-imagine-video`
  * `grok-imagine-video-1.5`
</ParamField>

<ParamField body="prompt" type="string" required>
  Non-empty instruction, at most 8000 Unicode characters

  `Array.from(prompt).length`
</ParamField>

<ParamField body="nsfw_check" type="boolean" default={false}>
  Whether to perform content moderation before submitting the video task.

  * `true`: Use `omni-moderation-latest` to review the prompt and input images
  * `false` or omitted: Do not send a moderation request, adding no moderation cost or latency (default)
</ParamField>

### Generation fields

<ParamField body="duration" type="integer" default={8}>
  Generation only; integer from 1 to 15, default 8
</ParamField>

<ParamField body="resolution" type="string" default="480p">
  Base: `480p/720p`; 1.5: `480p/720p/1080p`; default `480p`

  * `grok-imagine-video`: `480p`, `720p`
  * `grok-imagine-video-1.5`: `480p`, `720p`, `1080p`
</ParamField>

<ParamField body="aspect_ratio" type="string" default="auto">
  Generation only; `auto`, `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, or `2:3`

  * `auto`
  * `1:1`, `16:9`, `9:16`
  * `4:3`, `3:4`, `3:2`, `2:3`
</ParamField>

<ParamField body="image_urls" type="string[]">
  Optional reference-image array; every item must be a public HTTPS URL; omit instead of sending an empty array

  * Every item must be a publicly accessible HTTPS URL; relative URLs, Data URLs, and raw Base64 are not supported.
  * Do not send aliases such as `image`, `images`, or `input_reference`.
  * Array order is preserved; duplicate URLs occupy multiple input slots and may be billed more than once.
</ParamField>

### Video-edit fields

<ParamField body="video" type="object">
  Source video object `{url}`; public HTTPS URL; base model only

  <Expandable title="URL">
    <ParamField body="url" type="string" required>
      HTTPS
    </ParamField>
  </Expandable>
</ParamField>

A video-edit request requires `model`, `prompt`, and `video`, and may optionally include `nsfw_check`. Do not send `duration`, `resolution`, `aspect_ratio`, or `image_urls`; the platform detects the source duration.

## TypeScript request types

Use a discriminated union so generation-only fields cannot be sent to video editing.

```ts theme={null}
type GrokVideoModel =
  | "grok-imagine-video"
  | "grok-imagine-video-1.5";

type GrokVideoResolution = "480p" | "720p" | "1080p";

type GrokVideoAspectRatio =
  | "auto"
  | "1:1"
  | "16:9"
  | "9:16"
  | "4:3"
  | "3:4"
  | "3:2"
  | "2:3";

interface GrokVideoGenerateRequest {
  model: GrokVideoModel;
  prompt: string;
  nsfw_check?: boolean;
  duration?: number;
  resolution?: GrokVideoResolution;
  aspect_ratio?: GrokVideoAspectRatio;
  image_urls?: string[];
}

interface GrokVideoEditRequest {
  model: "grok-imagine-video";
  prompt: string;
  nsfw_check?: boolean;
  video: { url: string };
}

type GrokVideoRequest =
  | GrokVideoGenerateRequest
  | GrokVideoEditRequest;
```

## Request examples

<Tabs>
  <Tab title="Text to video">
    ```json theme={null}
    {"model":"grok-imagine-video","prompt":"A cinematic aerial shot at sunrise","duration":5,"resolution":"720p","aspect_ratio":"16:9"}
    ```
  </Tab>

  <Tab title="1.5 · 1080p">
    ```json theme={null}
    {"model":"grok-imagine-video-1.5","prompt":"A smooth studio product commercial","duration":5,"resolution":"1080p","aspect_ratio":"16:9"}
    ```
  </Tab>

  <Tab title="Single or multiple reference images">
    ```json theme={null}
    {
      "model":"grok-imagine-video-1.5",
      "prompt":"Use the first image as subject and the second as style",
      "duration":5,
      "resolution":"720p",
      "aspect_ratio":"16:9",
      "image_urls":[
        "https://cdn.example.com/subject.jpg",
        "https://cdn.example.com/style.jpg"
      ]
    }
    ```
  </Tab>

  <Tab title="Video editing">
    ```json theme={null}
    {
      "model":"grok-imagine-video",
      "prompt":"Improve motion consistency and apply cinematic color grading",
      "video":{"url":"https://cdn.example.com/source.mp4"}
    }
    ```
  </Tab>
</Tabs>

## Asynchronous tasks

### Create success

A successful create request returns HTTP `200`. Save `data[0].task_id`; submission does not mean the video is complete. A task ID means submitted, not completed.

```json theme={null}
{
  "code":200,
  "data":[{"status":"submitted","task_id":"task_01M09Y4Y101HTFFPV2QPW9DQ3W"}]
}
```

### Query a task

```http theme={null}
GET https://api.apimart.ai/v1/tasks/{task_id}
Authorization: Bearer <APIMART_API_KEY>
Accept: application/json
```

Poll `GET /v1/tasks/{task_id}` every 3–5 seconds. Resume polling with the saved task ID after a page refresh.

| `data.status` | Meaning             | Frontend action                          |
| ------------- | ------------------- | ---------------------------------------- |
| `pending`     | Queued              | Continue polling                         |
| `processing`  | Generating          | Show progress and continue               |
| `completed`   | Completed           | Read the result and stop                 |
| `failed`      | Failed and refunded | Show the error and stop                  |
| `unknown`     | Temporarily unknown | Reduce polling frequency and retry later |

### Completed response

```json theme={null}
{
  "code":200,
  "data":{
    "id":"task_xxx",
    "status":"completed",
    "progress":100,
    "created":1787040038,
    "completed":1787040081,
    "actual_time":43,
    "estimated_time":100,
    "cost":0.072,
    "credits_cost":0.72,
    "result":{"videos":[{"url":["https://cdn.example.com/result.mp4"],"expires_at":1787126481}]}
  }
}
```

`result.videos[0].url` is an array of strings, not a single string. Validate every value as an HTTPS URL before display. Runtime validation is recommended:

```ts theme={null}
function extractVideoURLs(payload: unknown): string[] {
  const groups = (payload as any)?.data?.result?.videos;
  if (!Array.isArray(groups)) return [];

  return groups.flatMap((group: any) =>
    Array.isArray(group?.url)
      ? group.url.filter(
          (url: unknown): url is string =>
            typeof url === "string" && /^https:///i.test(url),
        )
      : [],
  );
}
```

Use `expires_at` as the source of truth for URL expiry. Do not hard-code a lifetime; prompt users to download or persist the result.

### Failed response

```json theme={null}
{
  "code":200,
  "data":{
    "id":"task_xxx",
    "status":"failed",
    "progress":100,
    "cost":0,
    "credits_cost":0,
    "error":{"message":"Task failed.","type":"task_failed","param":"","code":"task_failed"}
  }
}
```

<Warning>
  A task query can return HTTP `200` while `data.status` is `failed`. Determine success from `data.status`; failed tasks have `cost=0`.
</Warning>

## Pricing catalog

```http theme={null}
GET https://api.apimart.ai/api/pricing/models/all
```

Read `GET /api/pricing/models/all` and find the model by `id` in `data.models.video`. Prices are estimates; the authoritative final amount is `data.cost` from the task response.

### Output-video pricing

```json theme={null}
{
  "fixed_prices":{
    "unit":"usd_per_second",
    "dimension":"resolution",
    "items":[
      {"key":"480P","original_price":0.05,"after_discount":0.04},
      {"key":"720P","original_price":0.07,"after_discount":0.056}
    ]
  }
}
```

* Pricing keys use uppercase `480P/720P/1080P`, while request values use lowercase; normalize case when looking up prices.
* `default` is compatibility metadata, not a selectable resolution.
* Use `after_discount` directly; do not apply the discount again.

### Input-material pricing

```json theme={null}
{"unit":"usd_per_image","original_price":0.002,"after_discount":0.0016}
```

```json theme={null}
{"unit":"usd_per_second","original_price":0.01,"after_discount":0.008}
```

Video input pricing is a scalar object. Do not require `items`, `billing_mode`, or `max_billable_seconds`. Model 1.5 has no video-input price because it cannot edit video.

### Estimate formulas

```text theme={null}
Generation estimate = discounted output price per second × duration + discounted image price × image count
Edit estimate = discounted 720P output price per second × source seconds + discounted video-input price × source seconds
```

User-specific pricing and server-side rounding can change the estimate. The final amount is always task `data.cost`.

## Frontend rules

### Model switching

* Base model shows only `480p/720p`; 1.5 also shows `1080p`.
* Switching from 1.5 `1080p` to base must fall back to `480p`.
* Video-edit mode fixes the model to `grok-imagine-video`.

### Mode switching

| Mode             | Visible controls                                     | Submitted fields                 | Must clear                                    |
| ---------------- | ---------------------------------------------------- | -------------------------------- | --------------------------------------------- |
| Generation       | `prompt/duration/resolution/aspect_ratio/nsfw_check` | Generation fields                | `image_urls/video`                            |
| Reference images | Generation fields + `image_urls`                     | Generation fields + `image_urls` | `video`                                       |
| Video editing    | `prompt/video/nsfw_check`                            | `model/prompt/video/nsfw_check`  | `duration/resolution/aspect_ratio/image_urls` |

`nsfw_check` is optional in every mode. Send `true` when moderation is enabled; omit it or send `false` when disabled.

Disable the run button when any of these conditions applies:

* Text mode omits `image_urls` and `video`.
* Reference mode sends `image_urls` and omits `video`.
* Video-edit mode clears all generation-only fields.
* Disable submission for an empty or over-limit prompt, invalid duration, unsupported resolution, invalid material URL, active upload, or duplicate submission.
* Limit prompts to 8000 Unicode characters and duration to integers from 1 to 15.
* Use public HTTPS URLs only; omit empty `image_urls`.

## Common errors

| HTTP / Status | Common cause                                           | Handling                                               |
| ------------- | ------------------------------------------------------ | ------------------------------------------------------ |
| `400`         | Invalid parameters, prompt limit, or unsupported enum  | Show the server message and identify the field         |
| `401`         | Missing or invalid API key                             | Do not retry; check server configuration               |
| `402`         | Insufficient balance                                   | Prompt the user to top up                              |
| `403`         | Missing model permission                               | Do not retry automatically                             |
| `409`         | Idempotency conflict or original request still running | Keep the original key and retry the same request later |
| `429`         | Rate limit                                             | Honor `Retry-After` or use exponential backoff         |
| `500/502/503` | Temporary service failure                              | Retry a bounded number of times with the original key  |
| `failed`      | Asynchronous task failure                              | Stop polling, show the error; cost is zero             |

## Frontend checklist

* Keep the API key only in the backend or BFF.
* Do not mix official model names with `grok-imagine-1.5-video-ext`.
* Limit prompts to 8000 Unicode characters and duration to integers from 1 to 15.
* Use public HTTPS URLs only; omit empty `image_urls`.
* Send only `model/prompt/video` plus optional `nsfw_check` for video editing, and use the base model.
* Read `data[0].task_id` on submit and determine the terminal state from `data.status`.
* Read output from `result.videos[].url[]` and respect `expires_at`.
* Use catalog prices for display and task `data.cost` for the final amount.
