Skip to main content
Base URL: https://api.freemodel.app/v1
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
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"
  }'
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"])
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();
Response
{
  "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

FieldTypeRequiredDescription
modelstringYesUse gemini-omni-t2v.
promptstringYesDescribe the video you want to generate.
secondsstringYesSet this field to the string "10". Videos are fixed at 10 seconds. Any other value returns an invalid_request error.
sizestringNoVideo dimensions. Defaults to 1280x720. See supported values below.
size valueAspect ratioResolution
1280x72016:9720p
1920x108016:91080p
720x12809:16Portrait
1024x10241:1Square

Poll video status

GET /videos/{id} Replace task_01HXYZ with the ID returned when you submitted the video.
curl https://api.freemodel.app/v1/videos/task_01HXYZ \
  -H "Authorization: Bearer $FREEMODEL_API_KEY"
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"])
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();
Response
{
  "id": "task_01HXYZ",
  "object": "video",
  "status": "in_progress"
}
StatusMeaningWhat to do
queuedThe request is waiting to start.Poll again in 10 to 20 seconds.
in_progressThe video is generating.Poll again in 10 to 20 seconds.
completedThe video is ready.Download it from the content endpoint.
failedThe 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.
curl https://api.freemodel.app/v1/videos/task_01HXYZ/content \
  -H "Authorization: Bearer $FREEMODEL_API_KEY" \
  -o video.mp4
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)
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();

Handle video errors

ConditionResponseAction
Your balance is insufficient.insufficient_user_quotaAdd prepaid balance before submitting another video.
seconds is missing or is not the string "10".invalid_requestSend "seconds": "10".
A status request returns failed.status: "failed"Do not retry the content download. Failed generations are automatically refunded.
See Errors for the invalid_request response format.
A 10-second video costs $0.25. See the models page for current rates.