Video generation API: submit a job, poll it, download the MP4
The short version
- Rendering takes minutes, so video generation is an async job: submit, poll, download.
- A successful submission returns HTTP 202 with a job id. Save the id at once; it is the only way to collect a video you have paid for.
- Each model bills a flat price per video when the submission is accepted. Polling and downloading are free.
- Never retry blindly after a timeout. The job may already be running and billed.
Three steps
1. POST /v1/videos/create -> job id (billed here)
|
2. GET /v1/videos/jobs/<id> -> status (free, repeat)
|
3. GET /v1/videos/jobs/<id>/content?index=0 -> MP4 (free)
These paths are separate from /v1/chat/completions. The API key is the same.
Each video model has its own create endpoint, listed in the video generation docs. /v1/videos/create is the one for Seedance 2.0 Fast (bytedance/seedance-2.0-fast), the model these examples use. The status and download endpoints are shared by every model.
Step 1: submit the job
curl -X POST https://api.fastmetal.ai/v1/videos/create \
-H "Authorization: Bearer $FASTMETAL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"prompt": "Close-up of molten metal pouring into a mold"}'
Success is HTTP 202, with the job id and a status in the body:
{
"id": "AbCdEf123456",
"status": "pending",
"poll_path": "/v1/videos/jobs/AbCdEf123456"
}
Code that checks for status_code == 200 will treat a good submission as a failure. Check for 202, or use raise_for_status().
Write the id down before anything else. It stands for one paid video, and without it you cannot fetch the result.
Duration, resolution and audio are fixed per model on the server. You send a prompt (and, on models that allow it, an integer seed); a request with other values gets HTTP 400. Fixing the spec is what keeps the price flat: no parameter can change what a video costs.
Step 2: poll until it completes
curl https://api.fastmetal.ai/v1/videos/jobs/<JOB_ID> \
-H "Authorization: Bearer $FASTMETAL_API_KEY"
The status moves through pending and in_progress to completed or failed. A render typically takes one to three minutes. Polling is free, because the charge was settled in step 1, but every 15 to 30 seconds is enough.
Step 3: download the MP4
curl "https://api.fastmetal.ai/v1/videos/jobs/<JOB_ID>/content?index=0" \
-H "Authorization: Bearer $FASTMETAL_API_KEY" \
-o out.mp4
Also free. index=0 is the first video. Download promptly: the finished file is kept upstream for a limited time only.
The whole flow in Python
import os
import time
import requests
BASE = "https://api.fastmetal.ai"
HEADERS = {"Authorization": f"Bearer {os.environ['FASTMETAL_API_KEY']}"}
def generate_video(prompt: str, create_path: str = "/v1/videos/create",
timeout_s: int = 900, interval_s: int = 20) -> bytes:
# 1. Submit (billed once accepted)
r = requests.post(f"{BASE}{create_path}", headers=HEADERS,
json={"prompt": prompt}, timeout=60)
r.raise_for_status()
job_id = r.json()["id"]
print(f"job_id={job_id}") # record this before anything else
# 2. Poll (free)
deadline = time.time() + timeout_s
while time.time() < deadline:
s = requests.get(f"{BASE}/v1/videos/jobs/{job_id}", headers=HEADERS, timeout=30)
s.raise_for_status()
status = s.json().get("status")
print(status)
if status == "completed":
break
if status == "failed":
raise RuntimeError(f"Generation failed: {s.json()}")
time.sleep(interval_s)
else:
raise TimeoutError(f"Still rendering; fetch it later with job_id={job_id}")
# 3. Download (free)
c = requests.get(f"{BASE}/v1/videos/jobs/{job_id}/content",
headers=HEADERS, params={"index": 0}, timeout=300)
c.raise_for_status()
return c.content
with open("out.mp4", "wb") as f:
f.write(generate_video("Close-up of molten metal pouring into a mold"))
On a timeout the function raises with the job id in the message, so the video can still be collected later.
Billing: one flat price per video
The billing model is unlike chat:
| Action | Billed |
|---|---|
| Accepted submission (HTTP 202) | Flat price per video, set per model |
| Rejected submission (4xx or 5xx) | Free |
| Polling | Free |
| Download | Free |
Token counts and prompt length have no effect on the price. The current per-video prices are on the pricing page.
The flat charge at submission drives the retry rules:
- After a 202, do not submit again. Each accepted submission is a new, separately billed job.
- After an error response, nothing was billed, and a retry is safe.
- After no response at all (a timeout or a dropped connection), the outcome is unknown. The job may have been accepted and billed, so do not resubmit blindly.
If an accepted job later ends as failed, contact support with its job id for a service credit.
From an agent
Agents run into a timeout problem. A render takes minutes, while many MCP clients give up on a request after about 60 seconds. If the client abandons the call, the response holding the paid job id goes with it.
The FastMetal MCP server's generate_video tool waits for a while and, if the video is not ready, returns the job id; get_video collects it later. A client of your own works the same way: keep each wait shorter than the caller's timeout. Setup is in the MCP guide.
Common mistakes
- Expecting the MP4 in one request. It is a job.
- Losing the job id. The charge is final at submission, and the id is the only handle on the video.
- Retrying after a missing response. The job may be running; you would pay twice.
- Polling every second. The render takes minutes; 15 to 30 seconds is plenty.
- Using video in an encrypted chat. The web chat does not offer video generation in encrypted conversations, because large generated files cannot yet be encrypted on the client.
FAQ
Does video generation finish in one request?
No. Submit a job, poll until it completes, then download the MP4. Rendering typically takes one to three minutes.
Am I billed each time I poll?
No. The only charge is the flat per-video price when the submission is accepted. Status checks and downloads are free.
Can I retry if the request seems to fail?
Only if you received an error response; that submission was not billed. If you received nothing, the job may already be running and billed, so check before you resubmit.
Can I choose the resolution or length?
No. Each model's spec is fixed on the server, which is what keeps its price flat. The specs are in the video generation docs.
Try it
Create an account, add credit (the minimum top-up is $5), and generate one video with the Python client above.