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

# Modelos oficiales de vídeo Grok

> Genera vídeo desde texto o imágenes de referencia con grok-imagine-video y grok-imagine-video-1.5, o edita un vídeo con el modelo base.

<Info>
  Esta página corresponde a los modelos oficiales `grok-imagine-video` y `grok-imagine-video-1.5`. Son distintos de `grok-imagine-1.5-video-ext`; no mezcles nombres ni parámetros.
</Info>

<Warning>
  Nunca expongas la API Key en el navegador, variables públicas, LocalStorage, URL o registros. Llama a APIMart desde tu backend o 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>

## Resumen de integración

Todos los modos usan el mismo endpoint asíncrono:

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

| Campos enviados             | Modo                           | Modelos                   |
| --------------------------- | ------------------------------ | ------------------------- |
| Sin `image_urls` ni `video` | Texto a vídeo                  | Ambos modelos             |
| `image_urls`                | Imágenes de referencia a vídeo | Ambos modelos             |
| `video`                     | Edición de vídeo               | Solo `grok-imagine-video` |

Tras enviar, guarda `data[0].task_id` y consulta:

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

<Warning>
  No envíes `X-APIMart-Response-Version`: cambia a una respuesta HTTP `202`. Esta página usa la respuesta asíncrona HTTP `200` anterior.
</Warning>

## Capacidades

| Capacidad                           | `grok-imagine-video` | `grok-imagine-video-1.5` |
| ----------------------------------- | :------------------: | :----------------------: |
| Texto a vídeo                       |           ✅          |             ✅            |
| Una o varias imágenes de referencia |           ✅          |             ✅            |
| Edición de vídeo                    |           ✅          |             ❌            |
| `480p`                              |           ✅          |             ✅            |
| `720p`                              |           ✅          |             ✅            |
| `1080p`                             |           ❌          |             ✅            |
| Duración: 1–15 segundos             |         1–15         |           1–15           |
| Prompt                              |        1–8000        |          1–8000          |

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

El contrato público no fija un máximo de imágenes. Conserva un array no vacío de URL válidas y su orden; no reutilices límites de modelos de imagen.

## Encabezados

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

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

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

<ParamField header="Idempotency-Key" type="string">
  `Idempotency-Key` es opcional y muy recomendable para solicitudes de pago. Admite 1–191 caracteres ASCII visibles; se recomienda UUID. Un reintento de red reutiliza la clave y el cuerpo originales. No cambies la clave si el resultado es incierto.

  Usa una clave nueva por operación lógica. Un reintento debe reutilizar la clave y el cuerpo originales.
</ParamField>

## Parámetros

### Campos comunes

<ParamField body="model" type="string" required>
  Nombre oficial; la edición solo admite el modelo base

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

<ParamField body="prompt" type="string" required>
  Instrucción no vacía, máximo 8000 caracteres Unicode

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

<ParamField body="nsfw_check" type="boolean" default={false}>
  Indica si se modera el contenido antes de enviar la tarea de vídeo.

  * `true`: Usa `omni-moderation-latest` para revisar el prompt y las imágenes de entrada
  * `false` o se omite: No solicita moderación ni añade coste o latencia de revisión (predeterminado)
</ParamField>

### Campos de generación

<ParamField body="duration" type="integer" default={8}>
  Solo generación; entero 1–15, predeterminado 8
</ParamField>

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

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

<ParamField body="aspect_ratio" type="string" default="auto">
  Solo generación; `auto`, `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2` o `2:3`

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

<ParamField body="image_urls" type="string[]">
  Array opcional; cada elemento debe ser una URL HTTPS pública; omitir si está vacío

  * Cada elemento debe ser una URL HTTPS pública; no se admiten URL relativas, Data URL ni Base64 sin formato.
  * No envíes alias como `image`, `images` o `input_reference`.
  * Se conserva el orden; las URL repetidas ocupan varias entradas y pueden cobrarse varias veces.
</ParamField>

### Campos de edición de vídeo

<ParamField body="video" type="object">
  Vídeo fuente `{url}` mediante HTTPS público; solo modelo base

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

La edición requiere `model`, `prompt` y `video`, y admite `nsfw_check` opcional. No envíes `duration`, `resolution`, `aspect_ratio` ni `image_urls`; la plataforma detecta la duración.

## Tipos de solicitud TypeScript

Usa una unión discriminada para impedir que los campos de generación lleguen a la edición.

```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;
```

## Ejemplos

<Tabs>
  <Tab title="Texto a vídeo">
    ```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="Una o varias imágenes de referencia">
    ```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="Edición de vídeo">
    ```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>

## Tareas asíncronas

### Creación correcta

La creación correcta devuelve HTTP `200`. Guarda `data[0].task_id`; el envío no significa que el vídeo esté listo. Un ID de tarea significa enviado, no completado.

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

### Consultar tarea

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

Consulta `GET /v1/tasks/{task_id}` cada 3–5 segundos. Tras recargar, reanuda con el ID guardado.

| `data.status` | Significado               | Acción                                  |
| ------------- | ------------------------- | --------------------------------------- |
| `pending`     | En cola                   | Seguir consultando                      |
| `processing`  | Generando                 | Mostrar progreso y continuar            |
| `completed`   | Completada                | Leer resultado y detener                |
| `failed`      | Fallida y reembolsada     | Mostrar error y detener                 |
| `unknown`     | Temporalmente desconocida | Reducir frecuencia y reintentar después |

### Respuesta completada

```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` es un array de cadenas, no una cadena. Valida cada valor como URL HTTPS. Se recomienda validar en tiempo de ejecución:

```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),
        )
      : [],
  );
}
```

Usa `expires_at` para la caducidad. No fijes un plazo; pide descargar o guardar el resultado.

### Respuesta fallida

```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>
  La consulta puede devolver HTTP `200` con `data.status=failed`. Decide por `data.status`; una tarea fallida tiene `cost=0`.
</Warning>

## Catálogo de precios

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

Lee `GET /api/pricing/models/all` y busca el `id` en `data.models.video`. Los precios son estimaciones; el importe final es `data.cost` de la tarea.

### Precio del vídeo de salida

```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}
    ]
  }
}
```

* Las claves de precio son `480P/720P/1080P` y los valores de solicitud usan minúsculas; normaliza al buscar.
* `default` es un dato de compatibilidad, no una resolución seleccionable.
* Usa `after_discount` directamente; no apliques otro descuento.

### Precio del material de entrada

```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}
```

El precio de vídeo es un objeto escalar. No exijas `items`, `billing_mode` ni `max_billable_seconds`. 1.5 no tiene precio de vídeo de entrada.

### Fórmulas de estimación

```text theme={null}
Generación = precio de salida/segundo × duración + precio por imagen × cantidad
Edición = precio 720P/segundo × segundos fuente + precio de entrada de vídeo × segundos fuente
```

El precio específico y el redondeo pueden variar. El importe final siempre es `data.cost`.

## Reglas del frontend

### Cambio de modelo

* Base muestra `480p/720p`; 1.5 añade `1080p`.
* Al pasar de 1.5 `1080p` a Base, volver a `480p`.
* La edición fija `grok-imagine-video`.

### Cambio de modo

| Modo                   | Controles visibles                                   | Campos enviados                     | Debe limpiarse                                |
| ---------------------- | ---------------------------------------------------- | ----------------------------------- | --------------------------------------------- |
| Generación             | `prompt/duration/resolution/aspect_ratio/nsfw_check` | Campos de generación                | `image_urls/video`                            |
| Imágenes de referencia | Campos de generación + `image_urls`                  | Campos de generación + `image_urls` | `video`                                       |
| Edición de vídeo       | `prompt/video/nsfw_check`                            | `model/prompt/video/nsfw_check`     | `duration/resolution/aspect_ratio/image_urls` |

`nsfw_check` es opcional en todos los modos. Envía `true` al activar la moderación; omítelo o envía `false` al desactivarla.

Desactiva el botón si se cumple alguna condición:

* Texto omite `image_urls` y `video`.
* Referencia envía `image_urls` y omite `video`.
* La edición limpia campos de generación.
* Desactivar con prompt o duración inválidos, resolución o URL no admitida, carga activa o envío duplicado.
* Prompt ≤8000 Unicode y duración entera 1–15.
* Solo URL HTTPS públicas; omitir `image_urls` vacío.

## Errores comunes

| HTTP / Estado | Causa                                      | Tratamiento                                |
| ------------- | ------------------------------------------ | ------------------------------------------ |
| `400`         | Parámetros, prompt o enum inválido         | Mostrar mensaje y campo                    |
| `401`         | API Key ausente o inválida                 | No reintentar; revisar servidor            |
| `402`         | Saldo insuficiente                         | Solicitar recarga                          |
| `403`         | Sin permiso del modelo                     | No reintentar automáticamente              |
| `409`         | Conflicto idempotente o solicitud en curso | Conservar clave y reintentar después       |
| `429`         | Límite de solicitudes                      | Respetar `Retry-After`                     |
| `500/502/503` | Fallo temporal                             | Reintentos limitados con la clave original |
| `failed`      | Tarea asíncrona fallida                    | Detener consulta; coste cero               |

## Comprobación del frontend

* Guardar la API Key solo en backend o BFF.
* No mezclar modelos oficiales con `grok-imagine-1.5-video-ext`.
* Prompt ≤8000 Unicode y duración entera 1–15.
* Solo URL HTTPS públicas; omitir `image_urls` vacío.
* En edición, envía solo `model/prompt/video` más `nsfw_check` opcional y usa el modelo base.
* Leer `data[0].task_id` y el final desde `data.status`.
* Leer `result.videos[].url[]` y respetar `expires_at`.
* Mostrar catálogo y usar `data.cost` final.
