Authorizationstring · headerrequiredOpen API v1 · Seedance 2.0
Put video generation behind your product
AiuniVid Open API is an asynchronous generation gateway. Create returns a task ID immediately. The website and API share the same credit ledger. It exposes Seedance 2.5 and Seedance 2.0 across text, image, and reference modes; 2.0 also has Fast and Mini tiers — twelve public model IDs.
- Text-to-Video: Generate from a text description.
- Image-to-Video: Use 1–2 images as first or first-and-last frames.
- Reference-to-Video: Use images, video, and audio as multimodal references.
Quickstart
Any signed-in user can create an av_live_ key in Developer Center. The secret is shown once — store it on your server.
- 1. Set environment variables
export AIUNIVID_BASE_URL=https://aiunivid.com/api/open export AIUNIVID_API_KEY=av_live_xxx - 2. Quote credits (optional, no charge)
POST the same body to /v1/videos/generations/quote and read data.credits_to_hold. - 3. Create the task
POST /v1/videos/generations with a required Idempotency-Key. Success is 202 plus a task id. Replaying the same key and body does not charge twice. - 4. Poll or receive a webhook
GET /v1/videos/generations/{id} until status is succeeded or failed — or pass callback_url on create. On success, use result.download_url.
Integrate into your video product
Keep AiuniVid behind your backend. Users talk only to your product; your service owns the API key and task sync.
Product UI
Submits prompt, model, and media.
Your backend
Validates the user, stores a record, then calls AiuniVid.
AiuniVid API
Returns 202 and a task ID.
Result page
Reads your task status and plays the video.
Rate limits
Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds). Exceeding the limit returns 429 rate_limit_exceeded.
| Class | Endpoints | Limit |
|---|---|---|
| Create endpoints | POST /v1/videos/generations, /quote, /{id}/final | 10 requests / minute / key |
| Read endpoints | GET /v1/models, /v1/credits/balance, /v1/videos/generations, /v1/videos/generations/{id} | 60 requests / minute / key |
Models and capabilities
GET /v1/modelsendpoint{
"success": true,
"request_id": "req_xxx",
"data": {
"models": [
{
"id": "seedance-2.0-text-to-video",
"name": "Seedance 2.0 Text-to-Video",
"mode": "text-to-video",
"tier": "standard",
"durations": [5, 8, 10],
"aspect_ratios": ["16:9", "9:16", "1:1"],
"qualities": ["480p", "720p", "1080p"],
"capabilities": {
"text_to_video": true,
"generate_audio": true,
"max_images": 0,
"max_videos": 0,
"max_audios": 0
}
}
]
}
}Quote a generation
POST /v1/videos/generations/quoteendpoint{
"success": true,
"request_id": "req_01JQ9X2B6XK9K4VQY2QZ4H6W3R",
"data": {
"model": "seedance-2.5-text-to-video",
"credits_to_hold": 50,
"currency_note": "credits"
}
}Create a generation
POST /v1/videos/generationsendpointrequiredmodelenum<string>required| Model ID | Mode | Tier |
|---|---|---|
| seedance-2.5-text-to-video | text-to-video | standard |
| seedance-2.5-image-to-video | image-to-video | standard |
| seedance-2.5-reference-to-video | reference-to-video | standard |
| seedance-2.0-text-to-video | text-to-video | standard |
| seedance-2.0-image-to-video | image-to-video | standard |
| seedance-2.0-reference-to-video | reference-to-video | standard |
| seedance-2.0-fast-text-to-video | text-to-video | fast |
| seedance-2.0-fast-image-to-video | image-to-video | fast |
| seedance-2.0-fast-reference-to-video | reference-to-video | fast |
| seedance-2.0-mini-text-to-video | text-to-video | mini |
| seedance-2.0-mini-image-to-video | image-to-video | mini |
| seedance-2.0-mini-reference-to-video | reference-to-video | mini |
promptstringrequiredimage_urlsstring<uri>[]video_urls / audio_urlsstring<uri>[]durationintegerqualityenum<string>aspect_ratioenum<string>generate_audiobooleandraftbooleancallback_urlstring<uri>metadataobjectTask management
GET /v1/videos/generations/{id}endpointGET /v1/videos/generationsendpoint{
"success": true,
"request_id": "req_xxx",
"data": {
"tasks": [{ "id": "vid_01JQ9X2B6XK9K4VQY2QZ4H6W3R", "status": "succeeded" }],
"next_cursor": null
}
}POST /v1/videos/generations/{id}/cancelendpointPOST /v1/videos/generations/{id}/finalendpointUpload reference media
POST /v1/uploads/presignendpoint{
"success": true,
"request_id": "req_xxx",
"data": {
"upload_url": "https://aiunivid.com/api/open/v1/uploads/put?key=api-uploads/...&sig=...",
"public_url": "https://cdn.example.com/api-uploads/user/file.png",
"expires_at": 1761314644,
"headers": { "Content-Type": "image/png" },
"method": "PUT"
}
}Credit balance
GET /v1/credits/balanceendpointBilling
The website and API use the same markup for the same user. 1 credit = $0.01.
| Stage | Behavior |
|---|---|
| quote | No charge; returns credits_to_hold |
| create | Reserves credits_reserved |
| succeeded | Settles credits_settled |
| failed / cancelled | Releases the hold as credits_refunded |
Webhooks
Pass callback_url on create. AiuniVid POSTs the full task object when the task reaches succeeded, failed, or cancelled.
X-AiuniVid-EventheaderX-AiuniVid-SignatureheaderNode
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyAiuniVidSignature(header, rawBody, secret) {
const parts = Object.fromEntries(
header.split(",").map((item) => item.split("="))
);
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (age > 300) throw new Error("Signature timestamp is stale");
const expected = createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
if (
!timingSafeEqual(Buffer.from(parts.v1, "utf8"), Buffer.from(expected, "utf8"))
) {
throw new Error("Invalid signature");
}
}Python
import hmac, hashlib, time
def verify_aiunivid_signature(header: str, raw_body: str, secret: str) -> None:
parts = dict(item.split("=", 1) for item in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
raise ValueError("Signature timestamp is stale")
expected = hmac.new(
secret.encode(),
f'{parts["t"]}.{raw_body}'.encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(parts["v1"], expected):
raise ValueError("Invalid signature")Delivery times out after 10 seconds. Non-2xx responses are retried with exponential backoff (1s, 2s, 4s, 8s, …) for up to 24 hours. Always return 2xx to acknowledge the event.
Producing a series
A short-form series is many small jobs that share the same characters and settings. Four habits keep it consistent and its cost predictable.
- Upload each character once. Upload the character image through /v1/uploads/presign, then pass the same public_url in image_urls on every request of the series and refer to it as @image1 in the prompt. Recognizable real faces are refused by every model, so use a generated or illustrated character.
- One request per shot. Keep each request to one story beat and assemble the shots in your own edit. Give every shot its own Idempotency-Key: replay the same key only when you never received a response, such as after a network drop. To retry a shot that failed, use a new key — replaying the same key within 24 hours returns the first result.
- Quote the batch before you submit it. Send every body to /v1/videos/generations/quote, compare the total with available_credits from /v1/credits/balance, and stop before submitting if the balance is short; quote and charge use one formula. Create endpoints allow 10 requests per minute per key, so pace a large batch instead of firing it at once.
- Collect results by webhook rather than tight polling. Pass callback_url on create; read endpoints allow 60 requests per minute per key, which a batch of polling loops reaches quickly. A failed shot releases its frozen credits automatically.
For keeping one character consistent from shot to shot, see the consistent character guide.
Advanced compatibility path
The primary path is /v1/videos/generations. If you already integrated BytePlus-style content[] payloads, /v1/contents/generations/tasks (and its quote endpoint) remains available. New integrations should not use this path.
POST /v1/contents/generations/tasksendpointError handling
Branch on the stable error.code, not the message text. 500 is safe to retry with the same Idempotency-Key.
| HTTP | error.code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed request body or fields |
| 400 | unsupported_model | Model ID is not one of the public IDs |
| 401 | invalid_api_key | Key is invalid, expired, or revoked |
| 402 | insufficient_credits | Not enough available credits |
| 403 | insufficient_scope | Key lacks the required scope |
| 403 | task_cancel_not_allowed | Task cancellation is not supported |
| 404 | task_not_found | Task does not exist or is not owned by this key |
| 409 | idempotency_conflict | Same Idempotency-Key reused with a different body |
| 422 | unsupported_capability | Fields exceed the model's capability |
| 422 | unsafe_input | Media or callback URL is not a public HTTPS address |
| 429 | rate_limit_exceeded | Rate limit exceeded |
| 429 | too_many_concurrent_jobs | In-flight generation jobs exceeded the account limit |
| 500 | internal_error | Retry with the same Idempotency-Key |
Machine-readable contract: openapi.yaml · api-reference.md · llms.txt