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

# Model Video Resmi Grok

> Buat video dari teks atau gambar dengan grok-imagine-video dan grok-imagine-video-1.5, atau edit video dengan model dasar.

<Info>
  Halaman ini untuk model resmi `grok-imagine-video` dan `grok-imagine-video-1.5`. Keduanya berbeda dari `grok-imagine-1.5-video-ext`; jangan campur nama atau parameternya.
</Info>

<Warning>
  Jangan paparkan API Key di browser, variabel publik, LocalStorage, URL, atau log. Panggil APIMart melalui backend atau 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>

## Ringkasan integrasi

Semua mode memakai endpoint asinkron yang sama:

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

| Field permintaan               | Mode                      | Model                      |
| ------------------------------ | ------------------------- | -------------------------- |
| Tanpa `image_urls` dan `video` | Teks ke video             | Kedua model                |
| `image_urls`                   | Gambar referensi ke video | Kedua model                |
| `video`                        | Edit video                | Hanya `grok-imagine-video` |

Setelah mengirim, simpan `data[0].task_id`, lalu polling:

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

<Warning>
  Jangan kirim `X-APIMart-Response-Version`; header ini mengaktifkan respons HTTP `202`. Halaman ini memakai format asinkron HTTP `200` lama.
</Warning>

## Kemampuan model

| Kemampuan                           | `grok-imagine-video` | `grok-imagine-video-1.5` |
| ----------------------------------- | :------------------: | :----------------------: |
| Teks ke video                       |           ✅          |             ✅            |
| Satu atau beberapa gambar referensi |           ✅          |             ✅            |
| Edit video                          |           ✅          |             ❌            |
| `480p`                              |           ✅          |             ✅            |
| `720p`                              |           ✅          |             ✅            |
| `1080p`                             |           ❌          |             ✅            |
| Durasi: 1–15 detik                  |         1–15         |           1–15           |
| Prompt                              |        1–8000        |          1–8000          |

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

Kontrak publik tidak menetapkan batas tetap jumlah gambar. Gunakan array URL valid yang tidak kosong dan pertahankan urutannya; jangan gunakan batas model gambar.

## Header permintaan

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

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

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

<ParamField header="Idempotency-Key" type="string">
  `Idempotency-Key` opsional dan sangat disarankan untuk permintaan berbayar. Mendukung 1–191 karakter ASCII terlihat; UUID disarankan. Retry jaringan memakai key dan body asli. Jangan ganti key jika hasil belum pasti.

  Gunakan Key baru untuk operasi logis baru. Percobaan ulang harus memakai Key dan body asli.
</ParamField>

## Parameter

### Field umum

<ParamField body="model" type="string" required>
  Nama model resmi; edit hanya model dasar

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

<ParamField body="prompt" type="string" required>
  Instruksi tidak kosong, maksimal 8000 Unicode

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

<ParamField body="nsfw_check" type="boolean" default={false}>
  Menentukan apakah moderasi konten dijalankan sebelum tugas video dikirim.

  * `true`: Gunakan `omni-moderation-latest` untuk memeriksa prompt dan gambar masukan
  * `false` atau dihilangkan: Tidak meminta moderasi sehingga tidak menambah biaya atau latensi pemeriksaan (default)
</ParamField>

### Field pembuatan

<ParamField body="duration" type="integer" default={8}>
  Hanya generasi; integer 1–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">
  Hanya generasi; `auto`, `1:1`, `16:9`, `9:16`, `4:3`, `3:4`, `3:2`, atau `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 opsional; setiap item URL HTTPS publik; hilangkan jika kosong

  * Setiap item harus berupa URL HTTPS publik; URL relatif, Data URL, dan Base64 mentah tidak didukung.
  * Jangan kirim alias seperti `image`, `images`, atau `input_reference`.
  * Urutan dipertahankan; URL duplikat memakai beberapa slot dan dapat ditagih lebih dari sekali.
</ParamField>

### Field pengeditan video

<ParamField body="video" type="object">
  Video sumber `{url}` HTTPS publik; hanya Base

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

Pengeditan video memerlukan `model`, `prompt`, dan `video`, serta dapat menyertakan `nsfw_check` secara opsional. Jangan kirim `duration`, `resolution`, `aspect_ratio`, atau `image_urls`; platform mendeteksi durasi sumber.

## Tipe permintaan TypeScript

Gunakan discriminated union agar field pembuatan tidak terkirim ke pengeditan.

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

## Contoh

<Tabs>
  <Tab title="Teks ke 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="Satu atau beberapa gambar referensi">
    ```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="Edit video">
    ```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>

## Tugas asinkron

### Berhasil membuat

Pembuatan berhasil mengembalikan HTTP `200`. Simpan `data[0].task_id`; pengiriman belum berarti video selesai. ID tugas berarti terkirim, bukan selesai.

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

### Periksa tugas

```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}` tiap 3–5 detik. Setelah reload, lanjutkan dengan ID tersimpan.

| `data.status` | Arti                      | Tindakan                         |
| ------------- | ------------------------- | -------------------------------- |
| `pending`     | Dalam antrean             | Lanjut polling                   |
| `processing`  | Sedang dibuat             | Tampilkan progres                |
| `completed`   | Selesai                   | Baca hasil dan berhenti          |
| `failed`      | Gagal dan direfund        | Tampilkan error dan berhenti     |
| `unknown`     | Sementara tidak diketahui | Kurangi frekuensi dan coba nanti |

### Respons selesai

```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` adalah array string, bukan string tunggal. Validasi setiap nilai sebagai URL HTTPS. Validasi runtime disarankan:

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

Gunakan `expires_at` untuk masa berlaku. Jangan hard-code durasi; minta pengguna mengunduh atau menyimpan.

### Respons gagal

```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>
  Kueri dapat memberi HTTP `200` saat `data.status=failed`. Tentukan hasil dari `data.status`; tugas gagal memiliki `cost=0`.
</Warning>

## Katalog harga

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

Baca `GET /api/pricing/models/all` dan cari `id` di `data.models.video`. Harga hanya estimasi; jumlah final adalah `data.cost`.

### Harga video keluaran

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

* Key harga memakai `480P/720P/1080P`, sedangkan nilai permintaan huruf kecil; normalkan saat mencari.
* `default` adalah metadata kompatibilitas, bukan resolusi pilihan.
* Gunakan `after_discount` langsung; jangan terapkan diskon lagi.

### Harga material masukan

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

Harga input video berupa objek skalar. Jangan wajibkan `items`, `billing_mode`, atau `max_billable_seconds`. Model 1.5 tidak memiliki harga input video.

### Rumus estimasi

```text theme={null}
Generasi = harga output/detik × durasi + harga gambar × jumlah
Edit = harga 720P/detik × detik sumber + harga input video × detik sumber
```

Harga khusus dan pembulatan server dapat berbeda. Nilai akhir selalu `data.cost`.

## Aturan frontend

### Peralihan model

* Base hanya `480p/720p`; 1.5 juga `1080p`.
* Dari 1.5 `1080p` ke Base kembali ke `480p`.
* Mode edit mengunci `grok-imagine-video`.

### Peralihan mode

| Mode             | Kontrol yang tampil                                  | Field terkirim                  | Harus dihapus                                 |
| ---------------- | ---------------------------------------------------- | ------------------------------- | --------------------------------------------- |
| Pembuatan        | `prompt/duration/resolution/aspect_ratio/nsfw_check` | Field pembuatan                 | `image_urls/video`                            |
| Gambar referensi | Field pembuatan + `image_urls`                       | Field pembuatan + `image_urls`  | `video`                                       |
| Pengeditan video | `prompt/video/nsfw_check`                            | `model/prompt/video/nsfw_check` | `duration/resolution/aspect_ratio/image_urls` |

`nsfw_check` bersifat opsional di semua mode. Kirim `true` saat moderasi aktif; hilangkan atau kirim `false` saat nonaktif.

Nonaktifkan tombol jika salah satu kondisi berikut terpenuhi:

* Mode teks menghilangkan `image_urls` dan `video`.
* Mode referensi mengirim `image_urls` tanpa `video`.
* Mode edit membersihkan field generasi.
* Nonaktifkan untuk prompt, durasi, resolusi, URL tidak valid, upload aktif, atau kiriman duplikat.
* Prompt ≤8000 Unicode dan durasi integer 1–15.
* Hanya URL HTTPS publik; hilangkan `image_urls` kosong.

## Error umum

| HTTP / Status | Penyebab                                 | Penanganan                     |
| ------------- | ---------------------------------------- | ------------------------------ |
| `400`         | Parameter, prompt, atau enum tidak valid | Tampilkan pesan dan field      |
| `401`         | API Key tidak ada/tidak valid            | Jangan retry; periksa server   |
| `402`         | Saldo kurang                             | Minta top up                   |
| `403`         | Izin model tidak ada                     | Jangan retry otomatis          |
| `409`         | Konflik idempoten/permintaan aktif       | Pertahankan key dan coba nanti |
| `429`         | Rate limit                               | Ikuti `Retry-After`            |
| `500/502/503` | Gangguan sementara                       | Retry terbatas dengan key asli |
| `failed`      | Tugas asinkron gagal                     | Hentikan polling; biaya nol    |

## Checklist frontend

* API Key hanya di backend atau BFF.
* Jangan campur model resmi dengan `grok-imagine-1.5-video-ext`.
* Prompt ≤8000 Unicode dan durasi integer 1–15.
* Hanya URL HTTPS publik; hilangkan `image_urls` kosong.
* Untuk pengeditan, kirim hanya `model/prompt/video` ditambah `nsfw_check` opsional dan gunakan model Base.
* Baca `data[0].task_id` dan akhir dari `data.status`.
* Baca `result.videos[].url[]` dan patuhi `expires_at`.
* Tampilkan katalog dan gunakan `data.cost` final.
