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();
{
"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.
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();
{
"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.
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
| 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 for the invalid_request response format.
A 10-second video costs $0.25. See the models page for current rates.