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

# Video generation

> Create, monitor, and download videos with the OpenAI Videos API.

<Info>
  Base URL: `https://api.freemodel.app/v1`
</Info>

Video generation is asynchronous. Submit a video request, poll its status until it completes, then download the MP4 file. Generation usually completes in two to five minutes. Poll every 10 to 20 seconds.

The currently available model is `gemini-omni-t2v` for text-to-video. `gemini-omni-i2v`, `gemini-omni-r2v`, and `gemini-omni-extend` are coming soon.

## Submit a video request

`POST /videos`

<CodeGroup>
  ```bash Curl theme={null}
  curl https://api.freemodel.app/v1/videos \
    -H "Authorization: Bearer $FREEMODEL_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "gemini-omni-t2v",
      "prompt": "A paper airplane glides through a sunlit library, cinematic tracking shot",
      "seconds": "10",
      "size": "1280x720"
    }'
  ```

  ```python Python theme={null}
  import json
  import os
  from urllib.request import Request, urlopen

  request = Request(
      "https://api.freemodel.app/v1/videos",
      data=json.dumps({
          "model": "gemini-omni-t2v",
          "prompt": "A paper airplane glides through a sunlit library, cinematic tracking shot",
          "seconds": "10",
          "size": "1280x720",
      }).encode(),
      headers={
          "Authorization": f"Bearer {os.environ['FREEMODEL_API_KEY']}",
          "Content-Type": "application/json",
      },
      method="POST",
  )

  with urlopen(request) as response:
      video = json.load(response)

  print(video["id"])
  ```

  ```javascript Node.js theme={null}
  async function main() {
    const response = await fetch("https://api.freemodel.app/v1/videos", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.FREEMODEL_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model: "gemini-omni-t2v",
        prompt: "A paper airplane glides through a sunlit library, cinematic tracking shot",
        seconds: "10",
        size: "1280x720",
      }),
    });

    if (!response.ok) {
      throw new Error(await response.text());
    }

    const video = await response.json();
    console.log(video.id);
  }

  main();
  ```
</CodeGroup>

```json Response theme={null}
{
  "id": "task_01HXYZ",
  "object": "video",
  "status": "queued",
  "progress": 0,
  "model": "gemini-omni-t2v",
  "created_at": 1784045139,
  "seconds": "10",
  "size": "1280x720"
}
```

Save the returned `id`. You use it to poll the request and download the completed video.

### Request body

| Field     | Type   | Required | Description                                                                                                              |
| --------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------ |
| `model`   | string | Yes      | Use `gemini-omni-t2v`.                                                                                                   |
| `prompt`  | string | Yes      | Describe the video you want to generate.                                                                                 |
| `seconds` | string | Yes      | Set this field to the string `"10"`. Videos are fixed at 10 seconds. Any other value returns an `invalid_request` error. |
| `size`    | string | No       | Video dimensions. Defaults to `1280x720`. See supported values below.                                                    |

| `size` value | Aspect ratio | Resolution |
| ------------ | ------------ | ---------- |
| `1280x720`   | 16:9         | 720p       |
| `1920x1080`  | 16:9         | 1080p      |
| `720x1280`   | 9:16         | Portrait   |
| `1024x1024`  | 1:1          | Square     |

## Poll video status

`GET /videos/{id}`

Replace `task_01HXYZ` with the ID returned when you submitted the video.

<CodeGroup>
  ```bash Curl theme={null}
  curl https://api.freemodel.app/v1/videos/task_01HXYZ \
    -H "Authorization: Bearer $FREEMODEL_API_KEY"
  ```

  ```python Python theme={null}
  import json
  import os
  from urllib.request import Request, urlopen

  video_id = "task_01HXYZ"
  request = Request(
      f"https://api.freemodel.app/v1/videos/{video_id}",
      headers={"Authorization": f"Bearer {os.environ['FREEMODEL_API_KEY']}"},
  )

  with urlopen(request) as response:
      video = json.load(response)

  print(video["status"])
  ```

  ```javascript Node.js theme={null}
  async function main() {
    const videoId = "task_01HXYZ";
    const response = await fetch(
      `https://api.freemodel.app/v1/videos/${videoId}`,
      { headers: { Authorization: `Bearer ${process.env.FREEMODEL_API_KEY}` } },
    );

    if (!response.ok) {
      throw new Error(await response.text());
    }

    const video = await response.json();
    console.log(video.status);
  }

  main();
  ```
</CodeGroup>

```json Response theme={null}
{
  "id": "task_01HXYZ",
  "object": "video",
  "status": "in_progress"
}
```

| Status        | Meaning                           | What to do                                                                     |
| ------------- | --------------------------------- | ------------------------------------------------------------------------------ |
| `queued`      | The request is waiting to start.  | Poll again in 10 to 20 seconds.                                                |
| `in_progress` | The video is generating.          | Poll again in 10 to 20 seconds.                                                |
| `completed`   | The video is ready.               | Download it from the content endpoint.                                         |
| `failed`      | The video could not be generated. | Do not download the video. FreeModel automatically refunds failed generations. |

## Download the completed video

`GET /videos/{id}/content`

Only download a video after its status is `completed`. The endpoint returns a binary `video/mp4` stream.

<CodeGroup>
  ```bash Curl theme={null}
  curl https://api.freemodel.app/v1/videos/task_01HXYZ/content \
    -H "Authorization: Bearer $FREEMODEL_API_KEY" \
    -o video.mp4
  ```

  ```python Python theme={null}
  import os
  from urllib.request import Request, urlopen

  video_id = "task_01HXYZ"
  request = Request(
      f"https://api.freemodel.app/v1/videos/{video_id}/content",
      headers={"Authorization": f"Bearer {os.environ['FREEMODEL_API_KEY']}"},
  )

  with urlopen(request) as response, open("video.mp4", "wb") as video_file:
      while chunk := response.read(64 * 1024):
          video_file.write(chunk)
  ```

  ```javascript Node.js theme={null}
  const { createWriteStream } = require("node:fs");
  const { Readable } = require("node:stream");
  const { pipeline } = require("node:stream/promises");

  async function main() {
    const videoId = "task_01HXYZ";
    const response = await fetch(
      `https://api.freemodel.app/v1/videos/${videoId}/content`,
      { headers: { Authorization: `Bearer ${process.env.FREEMODEL_API_KEY}` } },
    );

    if (!response.ok || !response.body) {
      throw new Error(await response.text());
    }

    await pipeline(Readable.fromWeb(response.body), createWriteStream("video.mp4"));
  }

  main();
  ```
</CodeGroup>

## Handle video errors

| Condition                                         | Response                  | Action                                                                            |
| ------------------------------------------------- | ------------------------- | --------------------------------------------------------------------------------- |
| Your balance is insufficient.                     | `insufficient_user_quota` | Add prepaid balance before submitting another video.                              |
| `seconds` is missing or is not the string `"10"`. | `invalid_request`         | Send `"seconds": "10"`.                                                           |
| A status request returns `failed`.                | `status: "failed"`        | Do not retry the content download. Failed generations are automatically refunded. |

See [Errors](/errors#video-request-error) for the `invalid_request` response format.

<Note>
  A 10-second video costs \$0.25. See the [models page](https://freemodel.app/models) for current rates.
</Note>
