Video generation API
One POST request with the same key as chat: a scene description in, a link to an mp4 out. Parameters, examples in three languages and the billing rules are collected here.
Endpoints and authentication
Video lives on the OpenAI-compatible entry point and uses the same key as text models: only the path changes. The Anthropic entry point does not generate video.
| Method | Path | Purpose |
|---|---|---|
| POST | https://api.tokenator.top/v1/videos | Generate a clip from a text prompt. The same handler also answers /v1/videos/generations — either path works. |
| GET | /v1/videos/{id}?model=… | The status of an upstream async job, if you were handed its identifier. |
| GET | /v1/videos/models | The catalog of video models available to this key, together with their parameters. |
The key goes in Authorization: Bearer sk-your-tokenator-key. There is no separate video key — generations are drawn from the same key as tokens.
Your first request
curl https://api.tokenator.top/v1/videos \ -H "Authorization: Bearer sk-your-tokenator-key" \ -H "Content-Type: application/json" \ -d '{ "model": "seedance-2.0-mini", "prompt": "a neon city in the rain, slow camera fly-through", "duration": 6, "resolution": "720p", "aspect_ratio": "16:9" }'
The answer arrives once the clip is ready and looks like any other generation response: a data array of links.
{
"created": 1755600000,
"data": [
{ "url": "https://generated-video.tokenator.top/6f2c1a9b.mp4" }
]
}The response carries exactly two fields: created, a unix timestamp, and data, one entry per requested clip. An entry has either url or b64_json — the whole file in base64 when the model answered inline. The proxy adds nothing else.
The link points at a separate file host — generated-video.tokenator.top, straight from the root of the host. It is neither the API address nor the provider's temporary URL: the file was downloaded and rehosted on our side, is served with Cache-Control: immutable and lives as long as it sits on the service's disk.
Which models do video
| Model | API ID | Duration | Resolution | Audio | Image input |
|---|---|---|---|---|---|
| Seedance 2.0 Mini | seedance-2.0-mini | 5, 10 seconds | 720p | yes | yes |
| Seedance 2.0 | seedance-2.0 | 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 seconds | 720p | yes | yes |
| HappyHorse 1.1 | happyhorse-1.1 | 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 seconds | 720p, 1080p | yes | yes |
| Omni | omni | 10 seconds | 720p | yes | yes |
| H3 | hailuo-3 | 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 seconds | 768p | yes | yes |
| Seedance 2.0 Fast | seedance-2.0-fast | 5, 10, 15 seconds | 720p | yes | yes |
| Seedance 2.5 | seedance-2.5 | 30 seconds | 720p | yes | yes |
| Grok Imagine Video 1.5 | grok-imagine-video-1.5 | 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 seconds | 480p, 720p, 1080p | yes | yes |
| Kling 3.0 | kling-3.0 | 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 seconds | 720p, 1080p | yes | no |
| Kling 3.0 Pro | kling-3.0-pro | 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 seconds | 720p, 1080p | yes | no |
The API returns the same list — with durations, resolutions, aspect ratios and defaults. That is safer than hard-coding parameters: the list changes together with the catalog.
curl https://api.tokenator.top/v1/videos/models \ -H "Authorization: Bearer sk-your-tokenator-key"
{
"object": "list",
"data": [
{
"id": "seedance-2.0-mini",
"object": "video_model",
"durations": [4, 6, 8],
"resolutions": ["480p", "720p", "1080p"],
"aspect_ratios": ["16:9", "9:16", "1:1"],
"audio": true,
"image_input": true,
"default_duration": 4,
"default_resolution": "720p",
"default_aspect_ratio": "16:9",
"cost": {
"480p|4": 1,
"720p|4": 2,
"720p|8": 4,
"1080p|8": 8
},
"cost_default": 2
}
]
}Request parameters
| Field | Type | What it does |
|---|---|---|
model | string | The API ID of a video model from the catalog. When omitted, the first enabled video model is used — better to pass it explicitly. |
prompt | string, required | The scene description. An empty string is refused with a 400. |
n | integer | One request, one clip. The field is accepted for compatibility, but anything above 1 is clamped to 1: send several requests to get several clips. |
duration | integer, seconds | Clip length. seconds and duration_seconds are accepted too — vendors expect those as strings ("5"), and a number is converted for you. The hard ceiling is 60 seconds; the exact list of values comes from the model. |
resolution | string | For example 720p. Synonyms are normalised: sd → 480p, hd → 720p, 1k and fhd → 1080p. |
aspect_ratio | string | The aspect ratio, for example 16:9. The x spelling (16x9) is understood as well. |
size | string | A frame size such as 1280x720. When aspect_ratio is absent, the ratio is derived from it. |
generate_audio | boolean | Ask the model to render sound. Only models that declare audio accept it. |
seed | integer | The generation seed — with the same value the result is reproducible as far as the model allows. |
frame_images | array | Starting frames, to animate an existing image. |
first_image_url | string | A link to the first frame — the clip starts from it. Any public http(s) or data: link will do, it does not have to be ours. Requires a model that declares a first frame — its catalog entry says so — otherwise 400 before any generation is claimed. |
last_image_url | string | A link to the last frame — the clip arrives at it. Same rules as the first frame. |
input_references | array | Style or character references. Together with frame_images — at most four images per request. |
reference_image_urls | array | Links to reference images, up to five. Several links mean multi-reference; the prompt can point at them as @Image1 where the model supports it. Requires a model with image input. |
reference_videos | array | Links to reference videos, up to three — the motion or cut the model follows. |
reference_audios | array | Links to reference audio, up to three — the voice or music the model matches. |
Fields you leave out are filled from the model's defaults, and values outside the declared list are refused before the provider is contacted — a rejected attempt costs no generations. The callback_url, webhook_url and provider fields are stripped and never forwarded upstream: callbacks would bypass your key.
How long it takes and what happens inside
A render takes from tens of seconds to a few minutes. The request is synchronous: the connection is held until the clip is ready, and to keep intermediate proxies from closing it on silence the service pads the response body with keep-alive whitespace. The final JSON arrives as the last chunk — an ordinary parser reads it as-is.
When the provider answers with an async job, the proxy polls it and returns the finished clip — you do not need your own polling loop. The total wait is capped at 15 min; after that you get a 504, the claimed generations are returned to the key, and the answer carries the upstream job_id — the render is still running, and GET /v1/videos/{id}?model=… picks up its result when it is done.
The practical consequence: raise your HTTP client timeout to 15 min and do not retry on timeout — a repeat request starts a second render.
curl "https://api.tokenator.top/v1/videos/vid_123?model=seedance-2.0-mini" \ -H "Authorization: Bearer sk-your-tokenator-key"
The model parameter matters here: it selects the provider to ask for the status. Without it the request goes to the first enabled video model.
Starting frames and references
Models that declare image input can animate an existing frame. Images go in frame_images, style or character references in input_references; at most four in total per request. Some models take links instead, in reference_image_urls (up to five), reference_videos and reference_audios (three each) — those fields travel upstream untouched, while the counts and the link scheme are checked on our side. If the model declares no image input, the request is refused with a 400 — before any generation is claimed.
The first and the last frame are a separate pair of fields: first_image_url and last_image_url, one link each. The clip starts from the first frame and arrives at the last; either one alone is fine. The link has to be public — http(s) or data: — and it can point anywhere, the file does not have to be uploaded to us. The model has to declare the frame, which its catalog entry says, otherwise 400. Vendors expect different shapes for this; the proxy rewrites the request into whichever one the chosen upstream reads, so these two fields are all you need.
{
"model": "seedance-2.0-mini",
"prompt": "the camera slowly pushes in, wind moves the leaves",
"duration": 4,
"frame_images": ["https://example.com/frame.jpg"]
}{
"model": "seedance-2.0-mini",
"prompt": "a smooth transition, the camera stays put",
"duration": 5,
"first_image_url": "https://example.com/first.jpg",
"last_image_url": "https://example.com/last.jpg"
}SDKs and ready-made examples
The video endpoint is not part of the typed surface of the OpenAI SDKs, so the convenient way to call it is a raw request. The key and the base URL stay the same as for chat — no separate client needed.
from openai import OpenAI client = OpenAI( api_key="sk-your-tokenator-key", base_url="https://api.tokenator.top/v1", timeout=900, ) result = client.post( "/videos", body={ "model": "seedance-2.0-mini", "prompt": "a neon city in the rain", "duration": 6, "resolution": "720p", }, cast_to=dict, ) print(result["data"][0]["url"])
import requests response = requests.post( "https://api.tokenator.top/v1/videos", headers={"Authorization": "Bearer sk-your-tokenator-key"}, json={ "model": "seedance-2.0-mini", "prompt": "a neon city in the rain", "duration": 6, "resolution": "720p", }, timeout=900, ) response.raise_for_status() print(response.json()["data"][0]["url"])
const response = await fetch("https://api.tokenator.top/v1/videos", { method: "POST", headers: { Authorization: "Bearer sk-your-tokenator-key", "Content-Type": "application/json", }, body: JSON.stringify({ model: "seedance-2.0-mini", prompt: "a neon city in the rain", duration: 6, resolution: "720p", }), signal: AbortSignal.timeout(900_000), }) const result = await response.json() console.log(result.data[0].url)
Ready-made snippets with your own key and address are in the key dashboard, next to the chat ones.
How video is billed
Video does not spend tokens. A key carries a separate video-generation counter, and one generation is one clip at the base resolution and the base duration. Heavier parameters cost proportionally more: the resolution multiplier is set by the operator (say 480p — 1×, 720p — 2×, 1080p — 4×), and duration scales against the base one. The final charge is clips × model multiplier × resolution multiplier × duration multiplier, rounded up.
You do not have to do the arithmetic: /v1/videos/models returns a cost field per model — a list of {"resolution", "seconds", "price"} objects, one per combination (carrying aspect_ratio too when the aspect ratio moves the price) — plus cost_default for the default parameters. When the resolution is given as a size, the price follows the short side: 1920x1080 is 1080p.
Generations are claimed before the provider is contacted and returned if no clip arrives: a failed render costs nothing. Video bundles are sold separately from tokens; a key with token_limit: -1 is a generation-only key with no access to text models.
curl https://api.tokenator.top/v1/tokens \ -H "Authorization: Bearer sk-your-tokenator-key"
{
"name": "my-key",
"limit": 1000000,
"used": 240000,
"remaining": 760000,
"image_limit": 0,
"image_used": 0,
"image_remaining": 0,
"video_limit": 20,
"video_used": 3,
"video_remaining": 17
}Limits
- One clip per request: an
nabove 1 is clamped to 1. - A clip is at most 60 seconds long, and never longer than the model declares.
- At most four input images per request (
frame_imagesandinput_referencescombined). - References: up to five images (
reference_image_urls), up to three videos and up to three audio files. The links must behttp(s)ordata:; anything else is refused with a400before a generation is claimed. - Some models cannot render from a prompt alone: their catalog page says «Reference: required», and a request without
reference_image_urls/reference_videos/frame_imagesis refused with a400immediately, without touching the provider. - Concurrent renders per key: 1. A request beyond that is not queued — it gets a
429with abusy_for_secondsfield. - The total wait for one request is capped at 15 min.
Errors
| Symptom | Cause | Fix |
|---|---|---|
400 prompt required | The body carries no scene description, or is not JSON at all. | Check Content-Type: application/json and a non-empty prompt. |
400 about duration, resolution or aspect ratio | The value is not in the list the model declares. | The error message lists the allowed values; /v1/videos/models returns the same list. |
400 does not generate video | The request named a text or image model. | Take a video model ID from the catalog. |
429 with a video_gen_limit | The key has run out of video generations. | Top up a video bundle in your account; the balance is visible in /v1/tokens. |
429 with a video_concurrent_limit | A render is already running on this key: parallel renders are limited. | The busy_for_seconds field shows how long the current render has been going. Queue requests instead of firing them in parallel. |
504 | The model did not finish rendering in the allotted time. | Try a shorter clip or a lower resolution. Claimed generations are refunded. |
502 Request error | An error on the provider side. Its text is not passed through to the client. | Retry — Tokenator fails over to the next provider on its own. If it persists, contact support. |
Trying it without code
The key dashboard has a Studio with an Images / Video switch: duration, resolution and aspect ratio are picked with buttons and finished clips land in a gallery. It is the same endpoint and the same generation counter — a convenient way to try a model before writing an integration.
FAQ
Do I need a separate key for video?
No. The same key as for chat works; only the request path changes. What is bought separately is the bundle of generations.
Can I generate video through the Anthropic format?
No. Video lives only on the OpenAI-compatible entry point — /v1/videos. For chat the two formats remain interchangeable.
How do I learn which durations and resolutions a model supports?
Ask /v1/videos/models: it returns the durations, resolutions and aspect_ratios lists along with the defaults. The same values are on the model page in the catalog.
Are generations charged when a render fails?
No. Generations are claimed before the request and returned to the key when no clip arrives — a provider error or a timeout costs nothing.
How long does the link to a finished video live?
The file is rehosted by the service and served from our address rather than the provider's temporary link. Download it right away if you need the clip for the long run.
Can I run several renders in parallel?
Per key — 1 at a time; further requests get a 429. One request returns one clip, so several clips means several requests, one after another.