Before you begin
- An account and a key. Register in the console, then create a key on the API Keys page. Keep it on your server, never in browser or mobile code.
- Balance. Video is charged per generated second. Top up on the Wallet page if needed.
- A model ID. This guide uses
wan3.0-video, the lowest per-second rate. Confirm it in Model Square. - Patience. A task usually takes minutes, and sometimes much longer. Plan to check back rather than hold one connection open.
- Submit
POST /v1/videos - Save the ID
id: task_... - Check
GET /v1/videos/{id} - Download
metadata.url
Set your key once in the terminal you will use:
export TOKENROC_API_KEY="your-api-key"
Step 1: Submit a task
Send the model, a prompt, a resolution, and a length in seconds:
curl https://api.tokenroc.com/v1/videos \
-H "Authorization: Bearer $TOKENROC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wan3.0-video",
"prompt": "A paper boat drifting across a shallow pool, soft daylight",
"size": "480P",
"duration": 2
}'
| Field | Required | Accepted values |
|---|---|---|
model | Yes | One of the five video model IDs under Choose a model. |
prompt | Yes | A description of the clip. |
size | No | Resolution: 480P, 720P, or 1080P for Wan; 720P or 1080P for Kling. Defaults to 720P, which costs twice as much as 480P on Wan. |
duration | No | Whole seconds. Defaults to 5. Wan accepts 2 to 30, Kling accepts 3 to 15. |
Step 2: Save the task ID
TokenRoc replies straight away. The response describes the task, not the video:
{
"id": "task_...",
"task_id": "task_...",
"object": "video",
"model": "wan3.0-video",
"status": "queued",
"progress": 0,
"created_at": 1790000000
}
Store id somewhere durable, such as your database, before you do anything else. It is the only way to find this video again. task_id holds the same value for older clients.
Step 3: Check the task
Ask for the task by its ID:
export TASK_ID="paste-the-id-from-step-2"
curl https://api.tokenroc.com/v1/videos/$TASK_ID \
-H "Authorization: Bearer $TOKENROC_API_KEY"
While the video is being made, the response looks like this:
{
"id": "task_...",
"object": "video",
"model": "wan3.0-video",
"status": "in_progress",
"progress": 30,
"created_at": 1790000000,
"completed_at": 1790000015,
"metadata": { "url": "" }
}
status | Meaning | What to do |
|---|---|---|
queued | Accepted and waiting to start. | Check again later. |
in_progress | Being generated. | Check again later. |
completed | Finished. metadata.url holds the video link. | Download the file now. |
failed | Generation did not succeed. error.code and error.message give the reason. | Read the reason, adjust the request, and submit a new task. |
- Check every 15 seconds or less often. TokenRoc refreshes each task from the provider about every 15 seconds, so checking faster returns the same answer.
- Decide on
statusonly.progressmoves in coarse steps, not a countdown.completed_atrecords the last update to the task and is set before the video is ready. - You can only read tasks created with your own account's keys.
Step 4: Download the video
When status is completed, metadata.url contains the link. The signed link below is shortened:
{
"id": "task_...",
"object": "video",
"model": "wan3.0-video",
"status": "completed",
"progress": 100,
"created_at": 1790000000,
"completed_at": 1790000300,
"metadata": { "url": "https://<provider-storage>/<video-file>.mp4?<signature>" }
}
curl -L -o boat.mp4 "PASTE_THE_URL_FROM_metadata.url"
A complete script
This Python script submits the task, checks it every 15 seconds, and saves the MP4 when it is ready. It uses only the standard library. To keep checking a task you already submitted, pass its ID: python make_video.py task_...
import json
import os
import sys
import time
import urllib.error
import urllib.request
API = "https://api.tokenroc.com/v1"
HEADERS = {
"Authorization": "Bearer " + os.environ["TOKENROC_API_KEY"],
"Content-Type": "application/json",
}
WAIT_SECONDS = 30 * 60 # how long this script waits; the task keeps running after it stops
def call(method, path, body=None):
data = None if body is None else json.dumps(body).encode()
request = urllib.request.Request(API + path, data=data, headers=HEADERS, method=method)
try:
with urllib.request.urlopen(request, timeout=60) as response:
return json.load(response)
except urllib.error.HTTPError as err:
sys.exit(f"HTTP {err.code}: {err.read().decode()}")
if len(sys.argv) > 1:
task_id = sys.argv[1]
else:
task = call("POST", "/videos", {
"model": "wan3.0-video",
"prompt": "A paper boat drifting across a shallow pool, soft daylight",
"size": "480P",
"duration": 2,
})
task_id = task["id"]
print("Submitted", task_id)
deadline = time.monotonic() + WAIT_SECONDS
while True:
task = call("GET", "/videos/" + task_id)
status = task["status"]
print(status, task.get("progress"))
if status == "completed":
urllib.request.urlretrieve(task["metadata"]["url"], task_id + ".mp4")
print("Saved", task_id + ".mp4")
break
if status == "failed":
sys.exit(f"Task failed: {task.get('error')}")
if time.monotonic() > deadline:
sys.exit(f"Stopped waiting while the task is {status}. "
f"Run this script again with {task_id} to keep checking.")
time.sleep(15)
Stopped waiting, or failed?
These are different things, and mixing them up costs money.
| What happened | What it means | What to do |
|---|---|---|
| Your script or client reached its own time limit. | Only your code stopped. The task is still running on TokenRoc, and you are not refunded, because it has not failed. | Check the same ID again later. Do not submit the prompt again. |
status is failed. | The task ended without a video. | The amount reserved for it is returned automatically. Fix the cause and submit a new task. |
status stays queued or in_progress for several hours. | Something may be wrong with the task. | Stop checking and contact TokenRoc with the task ID. The Task Logs page in the console shows TokenRoc's own record of it. |
A charge in your usage log does not mean the video is ready. The charge is reserved when you submit, so it appears while the task is still in_progress.
What it costs
Video is priced per generated second:
cost = seconds generated x price per second x resolution / audio factor
- At submission, TokenRoc reserves the cost of the
durationyou asked for, at the resolution and audio setting you chose. If your balance cannot cover it, the task is not created. - When the task completes, the charge is settled against the length the provider reports it actually produced. If that differs from the reservation, the difference is refunded or charged.
- If the task fails, the reserved amount is returned.
Worked example. The request in this guide is 2 seconds of wan3.0-video at 480P. At $0.0448 per second, that is 2 x $0.0448 = $0.0896. Our own test tasks with these settings completed on 24 September 2026 and were charged that amount. The same 2 seconds at the default 720P would be twice that.
Wan models also accept "duration": -1, which lets the provider choose the length. TokenRoc then reserves the maximum of 30 seconds and settles to the length actually generated.
Prices were checked on 24 September 2026. Model Square shows the current rate.
Choose a model
All five models use the same submit and check steps. Change model, and keep size and duration within the model's range.
| Model ID | Good for | Resolution | Seconds | Price, 24 Sep 2026 |
|---|---|---|---|---|
wan3.0-video | Text-to-video at the lowest rate. | 480P, 720P, 1080P | 2 to 30 | $0.0448/s at 480P. 720P is 2x, 1080P is 4x. |
wan3.0-video-prime | The Prime tier of Wan 3.0, with the same options. | 480P, 720P, 1080P | 2 to 30 | $0.0672/s at 480P. 720P is 2x, 1080P is 4x. |
kling/kling-v3-video-generation | Video from a prompt, a first frame, or first and last frames. Optional audio. | 720P, 1080P | 3 to 15 | $0.0896/s at 720P, silent. 1080P is 1.33x; audio is 1.5x at 720P, 2x at 1080P. |
kling/kling-v3-omni-video-generation | Video guided by reference images or a reference video. Optional audio without a reference video. | 720P, 1080P | 3 to 15; 3 to 10 with a reference video | $0.0896/s at 720P, silent. 1080P is 1.33x; audio or a reference video is 1.5x at 720P, 2x at 1080P. |
kling/kling-v3-turbo-video-generation | Video with an audio track always included. | 720P, 1080P | 3 to 15 | $0.1195/s at 720P with audio. 1080P is 1.25x. |
Options for Kling models
- Audio: add
"metadata": {"parameters": {"audio": true}}. Wan models reject audio. Turbo always has audio and rejects"audio": false. Omni cannot combine audio with a reference video. - Aspect ratio: for a prompt-only Kling video, add
"metadata": {"parameters": {"aspect_ratio": "9:16"}}. Accepted values are16:9(default),9:16, and1:1. When you supply a frame, the frame sets the shape. - Frames: pass a public image URL in
imageto use it as the first frame. The standard model also accepts"images": [first, last]for first and last frames. - Omni references: reference images and a reference video are sent as typed entries in
metadata.input.media, using"type": "refer"for an image and"type": "feature"for a video.
TokenRoc checks these combinations before a task is created and rejects any it does not support, so an invalid request is never charged.
Errors
Problems found when you submit a task come back with a code and a message:
{
"code": "invalid_request",
"message": "duration must be between 2 and 30 seconds (got 1)",
"data": null
}
| What you see | Meaning | What to do |
|---|---|---|
| HTTP 401 | The key is missing, wrong, or disabled. | Check the Authorization: Bearer header and the key on the API Keys page. |
invalid_request or missing_model (400) | A field is missing or outside the model's range: prompt, duration, resolution, audio, or media. | Fix the field named in the message. Nothing was charged. |
insufficient_user_quota (403) | Your balance cannot cover the reservation. | Top up on the Wallet page, or ask for fewer seconds or a lower resolution. |
model_not_found (503) | The model ID is wrong or not available to your key. | Copy the ID again from Model Square. |
ali_api_error or fail_to_fetch_task | The provider refused the task when it was submitted, for example because of its content rules. | Read the message and change the prompt or inputs. Nothing was charged. |
task_not_exist (400) when checking | No task with that ID belongs to your account. | Check the ID, and use a key from the account that submitted the task. |
| HTTP 429 | Too many requests, or the provider is busy. | Wait, then retry with exponential backoff. |
Errors raised before a request reaches the video service, such as model_not_found, use an error object instead, like the one in the image guide. The status page shows whether the TokenRoc gateway responds. It does not report the health of each model.
Check usage
- Task Logs list every video task with its status. Use it to find a task ID you did not save.
- Usage Logs show the reservation at submission and any refund or extra charge at settlement.
- The Wallet page shows your balance.
Next steps
- Rerun the example at
720Por with a longerdurationonce the 2-second clip works. - Create still images with the image generation guide.
- Read the API quickstart for text models and the endpoint list.
Stuck? Contact TokenRoc with the model ID and task ID. Never send your API key or a signed video link.