Send a text prompt to a video generation model and receive a video file in return.
Text-to-video generation takes a natural-language description and produces a short video clip. It uses the same base URL and authentication as text completions, through the dedicated /v1/videos endpoint. Generation is asynchronous: you submit a job and poll for the result.
curl https://opendunes.com/v1/videos \
-H "Authorization: Bearer $OPENDUNES_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/veo-3.1",
"prompt": "A lone camel walks across golden sand dunes at sunset, cinematic wide shot",
"duration": 6
}'The endpoint responds 202 Accepted with a job id and its polling URL:
{
"id": "019383ab-1234-7000-a000-000000000001",
"polling_url": "https://opendunes.com/v1/videos/019383ab-1234-7000-a000-000000000001",
"status": "pending"
}| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Video model slug, from GET /v1/videos/models |
prompt | string | Yes | Text description of the desired video |
duration | integer | No | Seconds of video. The lengths a model takes are its supported_durations on GET /v1/videos/models |
import os, time, httpx
headers = {"Authorization": f"Bearer {os.environ['OPENDUNES_API_KEY']}"}
job = httpx.post(
"https://opendunes.com/v1/videos",
headers=headers,
json={"model": "google/veo-3.1",
"prompt": "A lone camel walks across golden sand dunes at sunset, cinematic wide shot",
"duration": 6},
).json()
while True:
status = httpx.get(f"https://opendunes.com/v1/videos/{job['id']}", headers=headers).json()
if status["status"] == "completed":
print(status["unsigned_urls"][0]) # the clip's download link
break
if status["status"] == "failed":
print("generation failed")
break
time.sleep(5)On completed, the job carries a download link for the clip in unsigned_urls, and the clip's id in Files in file_ids:
{
"id": "019383ab-1234-7000-a000-000000000001",
"status": "completed",
"unsigned_urls": [
"https://opendunes.com/v1/videos/019383ab-1234-7000-a000-000000000001/content?index=0"
],
"file_ids": [
"019383ac-5678-7000-b000-000000000002"
]
}Video models respond well to prompts that specify:
wide shot, close-up, tracking shot, slow motion.cinematic, documentary, animation, 4K.Bad: "a city"
Good: "Drone shot flying over Algiers at dawn, the Mediterranean coast visible to the north, golden light, cinematic 4K"
The link does not expire, and the clip is kept until you delete it. Download it with the same Authorization header; downloading is free, every time:
import httpx, pathlib
resp = httpx.get(status["unsigned_urls"][0], headers=headers)
resp.raise_for_status()
pathlib.Path("output.mp4").write_bytes(resp.content)Video is billed per second of output in DA. Nothing is charged at submission: the wallet moves once, when the job completes. A failed or timed-out job charges nothing. Per-second rates appear in the model catalog. See the video API reference.