How to Generate AI Video with PixVerse V6 via the PixVerse API
6 min read • June 8, 2024 (June 15, 2026)
Table of contents
- Introduction
- Generate a video in two API calls
- Supported models
- Audio, multi-shot & effects
- Pricing
- Batch-generate with a script
- Examples
- Frequently asked questions
- Conclusion
Introduction
Yes — you can generate PixVerse V6 video through a REST API. useapi.net provides a PixVerse API for PixVerse.ai that drives your own PixVerse account: you send a text or image prompt to one POST endpoint, poll a GET endpoint for the result, and get back an MP4.
The default model is the native PixVerse V6 — up to 15-second 1080p clips with integrated audio and multi-shot storytelling — and the same videos/create endpoint also exposes the rest of the native family (v5.6, v5.5, v5, v5-fast, pixverse-c1) plus third-party models such as Seedance 2.0, Kling O3/V3, Veo 3.1, Sora 2, Grok Imagine, and HappyHorse — all selectable with a single model field. This guide shows the two-call video workflow with copy-paste curl, then a runnable Node.js script that batch-generates from a list of prompts.
Generate a video in two API calls
You need a useapi.net API token and a connected PixVerse account. Generation is asynchronous: the create call returns a video_id immediately, then you poll until the video is ready.
1. Submit the job — POST https://api.useapi.net/v2/pixverse/videos/create:
curl -X POST "https://api.useapi.net/v2/pixverse/videos/create" \
-H "Authorization: Bearer $USEAPI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "v6",
"prompt": "A red panda eating bamboo in a misty forest, camera slowly pushing in",
"quality": "720p",
"duration": 5,
"aspect_ratio": "16:9"
}'
The response returns immediately with a video_id:
{
"video_id": "user:1234-pixverse:jo***@gmail.com-video:112233445566"
}
2. Poll for the result — GET https://api.useapi.net/v2/pixverse/videos/{video_id}:
curl "https://api.useapi.net/v2/pixverse/videos/VIDEO_ID" \
-H "Authorization: Bearer $USEAPI_TOKEN"
While the video is still processing this endpoint returns 404. Once it is done it returns 200 with video_status_name: "COMPLETED" and video_status_final: true. The finished MP4 is in the url field:
{
"video_id": "user:1234-pixverse:jo***@gmail.com-video:112233445566",
"model": "v6",
"prompt": "A red panda eating bamboo in a misty forest, camera slowly pushing in",
"quality": "720p",
"duration": 5,
"url": "https://media.pixverse.ai/....mp4",
"video_status_name": "COMPLETED",
"video_status_final": true
}
Prefer not to poll? Pass a replyUrl in the create body to receive a webhook callback (same JSON shape) when the video completes or fails.
Supported models
Pick a model per request with the model field (default v6). Native PixVerse models support integrated audio, the off-peak discount, preview mode, and seed; see Model Capabilities for the full per-model quality / duration / aspect-ratio matrix.
| Model id | Highlights | Qualities | Durations |
|---|---|---|---|
v6 (default) | Multi-shot, integrated audio, longest clips | 360p, 540p, 720p, 1080p | 1–15s |
v5.6 | Integrated audio | 360p, 540p, 720p, 1080p | 1–10s (1080p ≤ 8s) |
v5.5 | Integrated audio | 360p, 540p, 720p, 1080p | 1–10s (1080p ≤ 8s) |
v5 | Lip-sync TTS + sound-effect prompts | 360p, 540p, 720p, 1080p | 1–10s (1080p ≤ 8s) |
v5-fast | Fastest native model | 360p, 540p, 720p, 1080p | 1–10s (1080p ≤ 8s) |
pixverse-c1 | Wide aspect-ratio support, 15s | 360p, 540p, 720p, 1080p | 1–15s |
Beyond the native family, the same videos/create endpoint accepts third-party models via the model field: seedance-2.0 / seedance-2.0-fast (ByteDance), kling-o3 / kling-v3 (Kuaishou), veo-3.1-lite / veo-3.1-standard / veo-3.1-fast (Google), sora-2 / sora-2-pro (OpenAI), grok-imagine (xAI), and happyhorse-1.0 (Alibaba). Each has its own quality, duration, and feature constraints — see Model Capabilities.
Audio, multi-shot & effects
Integrated audio. Set audio: true to generate voice, dialogue, and background sound in a single step. It is a toggle on v6, v5.6, v5.5, pixverse-c1, the Seedance models, and the Kling models; on veo-3.1-standard, veo-3.1-fast, and happyhorse-1.0 audio is always on. (Legacy v5 instead uses lip_sync_tts_prompt and sound_effect_prompt.)
Multi-shot storytelling. On v6 only, set multi_shot: true to have the model generate multiple camera angles and scene cuts within one clip instead of a single continuous shot.
900+ effect templates. List the catalog with GET videos/effects, then pass the chosen template_id to videos/create along with the required input image(s). When template_id is set, the template controls model, duration, and quality — you only supply the frame path(s).
For more workflows on native PixVerse models, the API also offers image-to-video (upload a frame via POST files, pass it as first_frame_path), extend, upscale, lip-sync, modify, and motion-control.
Pricing
You keep your own PixVerse subscription (Pro, Premium, or Ultra) and add a single flat $15/month to useapi.net that covers API access to every supported service — there are no per-call API surcharges.
Each generation spends credits from your PixVerse plan. For native PixVerse models you can cut that cost with off_peak_mode: true (results usually delivered within 24 hours): Pro 30% off, Premium 50% off, Ultra unlimited free. preview_mode: true is another native-only option — a fast, lower-quality preview at 20% off that you can later upscale. Third-party models don’t support off-peak or preview mode; Ultra subscribers get a 40% discount on them instead. Pro+ plans additionally include unlimited image generation in Relax Mode for select models (Ultra covers all of them).
For an interactive per-model, per-plan cost calculator, see the PixVerse API overview.
Batch-generate with a script
Finding the right shot takes many attempts, and running them by hand is tedious. The Node.js script below reads a list of prompts from prompts.json, uploads any input frame images, submits each videos/create job, then polls and downloads every finished MP4 — so you can queue a batch and come back to the winners.
You need Node.js v21 or newer. Put prompts.json and pixverse.mjs in the same folder and run node ./pixverse.mjs API_TOKEN EMAIL, where API_TOKEN is your useapi.net API token and EMAIL is your connected PixVerse account email. The script looks the account up by email automatically.
Expand prompts.json
[
{
"prompt": "By default the v6 model with a 5-second 720p 16:9 clip will be used."
},
{
"model": "v6",
"duration": 15,
"quality": "1080p",
"aspect_ratio": "16:9",
"audio": true,
"multi_shot": true,
"prompt": "PixVerse V6 with integrated audio and multi-shot storytelling. A dramatic courtroom scene with multiple camera angles. For all parameters see https://useapi.net/docs/api-pixverse-v2/post-pixverse-videos-create-v4"
},
{
"model": "v5.6",
"duration": 8,
"quality": "1080p",
"first_frame_path": "./first_frame.webp",
"audio": true,
"prompt": "Image-to-video: the local image is uploaded via POST files and passed as first_frame_path. Note: aspect_ratio is derived from the image and must NOT be set for i2v."
},
{
"model": "seedance-2.0",
"duration": 5,
"quality": "720p",
"aspect_ratio": "16:9",
"prompt": "Third-party models (seedance-2.0, kling-o3, veo-3.1-fast, sora-2, grok-imagine, happyhorse-1.0, …) are selectable via the model field. They reject native-only flags such as off_peak_mode, preview_mode, multi_shot and seed."
}
]
Expand pixverse.mjs script
/*
Script version 1.0, June 15, 2026
Script to batch-generate videos using prompts with the PixVerse API v2 by useapi.net 🚀
Uses the POST /videos/create endpoint (default model: v6) and polls GET /videos/{video_id}.
For more details visit https://useapi.net/docs/api-pixverse-v2/post-pixverse-videos-create-v4
Installation Instructions:
==========================
You need Node.js v21 or newer installed to run this script. Download and install Node.js from:
- Windows, macOS, Linux: https://nodejs.org/
After installation, verify by running the following command in a terminal:
node -v
Running the Script:
===================
Usage: node pixverse.mjs <API_TOKEN> <EMAIL> [PROMPTS_FILE]
Replace API_TOKEN with your actual useapi.net API token, see https://useapi.net/docs/start-here/setup-useapi
Replace EMAIL with configured PixVerse email account, see https://useapi.net/docs/start-here/setup-pixverse
If optional PROMPTS_FILE not provided prompts.json will be used.
Example:
--------
node pixverse.mjs user:1234-abcdefhijklmnopqrstuv [email protected]
This command executes the script using API token user:1234-abcdefhijklmnopqrstuv with [email protected] PixVerse account email.
Changelog:
==========
- June 15, 2026: Initial release.
*/
import readline from 'node:readline';
import fs from 'fs/promises';
import { writeFile } from 'node:fs/promises';
import { Readable } from 'node:stream';
// Constants
const RESULTS_FILE = 'pixverse_results.txt';
const ERRORS_FILE = 'pixverse_errors.txt';
const DEFAULT_PROMPTS_FILE = 'prompts.json';
const DEFAULT_MODEL = 'v6';
const SLEEP_429 = 30 * 1000; // in milliseconds
const SLEEP_POLL = 15 * 1000; // in milliseconds
const urlAccounts = 'https://api.useapi.net/v2/pixverse/accounts';
const urlCreate = 'https://api.useapi.net/v2/pixverse/videos/create';
const urlVideo = 'https://api.useapi.net/v2/pixverse/videos/'; // + video_id (poll)
const urlFiles = 'https://api.useapi.net/v2/pixverse/files/'; // + ?email= (upload)
// PixVerse accepts these image extensions for uploaded frames.
const supportedFileExtensions = ['png', 'jpeg', 'jpg', 'gif', 'webp'];
// { filename: path }
const uploadedFiles = {};
// Utility to sleep for given milliseconds
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Function to fetch configured PixVerse API accounts
async function fetchAccounts(apiToken) {
const response = await fetch(urlAccounts, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`
}
});
if (!response.ok) {
console.error(`⛔ Error fetching accounts (HTTP ${response.status}): ${response.statusText}`);
process.exit(1);
}
return response.json();
}
const elapsedTimeSec = (start) => (Date.now() - start) / 1000;
// Map a file extension to the Content-Type required by POST /files
const contentTypeForExt = (ext) =>
ext === 'png' ? 'image/png' :
ext === 'gif' ? 'image/gif' :
ext === 'webp' ? 'image/webp' : 'image/jpeg';
// Upload a local image and return its PixVerse `path` (used as first_frame_path etc.)
async function uploadFile(apiToken, email, filename) {
// Check if already uploaded
if (uploadedFiles.hasOwnProperty(filename))
return uploadedFiles[filename];
const startTime = Date.now();
console.log(`⬆️ Account ${email} uploading file…`, filename);
const body = new Blob([await fs.readFile(filename)]);
const fileExt = filename.split('.').pop().toLowerCase();
const response = await fetch(`${urlFiles}?email=${encodeURIComponent(email)}`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`,
'Content-Type': contentTypeForExt(fileExt)
},
body
});
if (response.ok) {
const json = await response.json();
// Image uploads return result[0].path; use that as the frame path.
const path = json?.result?.[0]?.path ?? json?.path;
console.log(`🆗 path (${elapsedTimeSec(startTime)} sec)`, path);
uploadedFiles[filename] = path;
}
else {
console.error(`❗ Unable to upload file HTTP ${response.status} (${elapsedTimeSec(startTime)} sec)`, await response.text());
// Do not attempt to upload failed file again
uploadedFiles[filename] = undefined;
}
return uploadedFiles[filename];
}
async function submit(apiToken, url, body, index, prompt) {
const createResponse = await fetch(url, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiToken}`
},
body
});
const createBody = await createResponse.text();
// POST /videos/create returns 200 with a video_id to poll.
if (createResponse.status == 200) {
const json = JSON.parse(createBody);
const videoId = json.video_id ?? json.image_id;
if (videoId) {
await fs.appendFile(RESULTS_FILE, `${videoId},#${index}:${prompt}\n`);
console.log(`✅ video_id`, videoId);
return 200;
} else {
const error = `No video_id found in HTTP 200 response`;
console.log(`❓ ${error}`, createBody);
await fs.appendFile(ERRORS_FILE, `${error},#${index}:${prompt}\n`);
return 500;
}
} else {
switch (createResponse.status) {
case 429:
console.log(`🔄️ Account busy / concurrent limit, retry on HTTP ${createResponse.status}`, createBody);
break;
case 400:
console.log(`🛑 Rejected request (validation)`, createBody);
await fs.appendFile(ERRORS_FILE, `${createResponse.status},#${index}:${prompt}\n`);
break;
case 412:
console.log(`🛑 Insufficient credits`, createBody);
break;
case 422:
console.log(`🛑 Moderated prompt`, createBody);
await fs.appendFile(ERRORS_FILE, `${createResponse.status},#${index}:${prompt}\n`);
break;
default:
console.log(`❗ FAILED with HTTP ${createResponse.status}`, createBody);
await fs.appendFile(ERRORS_FILE, `${createResponse.status},#${index}:${prompt}\n`);
}
return createResponse.status;
}
}
// Submit a single prompt to POST /videos/create.
// first_frame_path (uploaded image) switches to image-to-video; aspect_ratio is then derived from the image.
async function submitVideo(apiToken, email, prompt, index) {
const {
model, prompt: text, first_frame_path, duration, quality, aspect_ratio,
audio, multi_shot, preview_mode, off_peak_mode, seed, template_id
} = prompt;
const useModel = model ?? DEFAULT_MODEL;
console.log(`🚀 ${useModel} » Prompt #${index} • account ${email} …`);
const framePath = first_frame_path ? await uploadFile(apiToken, email, first_frame_path) : undefined;
const body = JSON.stringify({
model: useModel,
email,
prompt: text,
first_frame_path: framePath,
duration,
quality,
// aspect_ratio applies only to text-to-video and is optional (the server defaults to 16:9 when omitted); for image-to-video it is derived from the image
aspect_ratio: framePath ? undefined : aspect_ratio,
audio,
multi_shot,
preview_mode,
off_peak_mode,
seed,
template_id
});
return await submit(apiToken, urlCreate, body, index, text);
}
// Function to poll and download videos
async function download(apiToken) {
if (! await fileExists(RESULTS_FILE)) return;
try {
const resultsContent = await fs.readFile(RESULTS_FILE, 'utf8');
const lines = resultsContent.trim().split('\n');
for (const line of lines) {
const [videoId, prompt] = line.split(',');
console.log(`👉 ${videoId}`);
while (true) {
const response = await fetch(`${urlVideo}${videoId}`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`
}
});
// 404 means the video is still processing — keep waiting.
if (response.status == 404) {
console.log(`⌛ ${videoId} still processing, waiting…`);
await sleep(SLEEP_POLL);
continue;
}
if (!response.ok) {
console.log(`🛑 Poll failed ${videoId} (HTTP ${response.status}):\n${prompt}\n`, await response.text());
break;
}
const job = await response.json();
const { url, video_status_name, video_status_final, error } = job;
if (error) {
console.error(`🛑 FAILED ${videoId} (${error}):\n${prompt}\n`);
break;
}
// Poll until the status is final (COMPLETED, MODERATED, etc.).
if (!video_status_final) {
console.log(`⌛ ${videoId} status (${video_status_name}) and is still in progress, waiting…`);
await sleep(SLEEP_POLL);
continue;
}
if (video_status_name !== 'COMPLETED' || !url) {
console.error(`🛑 ${videoId} finished as ${video_status_name} with no url:\n${prompt}\n`);
break;
}
const videoFilename = `${videoId.replace(/[:*]/g, '_')}.mp4`;
try {
await fs.access(videoFilename);
console.log(`⚠️ ${videoFilename} already exists. Skipping download.`);
break;
} catch {
// File does not exist, proceed with downloading
}
console.log(`✅ Downloading ${url} to ${videoFilename}`);
try {
const videoResponse = await fetch(url);
if (!videoResponse.ok) {
console.error(`⛔ Unable to download ${videoId} (HTTP ${videoResponse.status}):\n${prompt}\n`, url);
break;
}
const stream = Readable.fromWeb(videoResponse.body);
await writeFile(videoFilename, stream);
} catch (err) {
console.error(`⛔ Error during download: ${err}`);
}
break;
}
}
} catch (error) {
console.log(`⛔ Error during download:`, error.stack || error);
}
}
// Main function
async function main() {
const apiToken = process.argv[2];
const email = process.argv[3];
const promptFile = process.argv[4] || DEFAULT_PROMPTS_FILE;
if (!apiToken || !email) {
console.error('Usage: node pixverse.mjs <API_TOKEN> <EMAIL> [PROMPTS_FILE]');
process.exit(1);
}
console.info('Script v1.0');
console.info('Node version is: ' + process.version);
try {
if (await fileExists(RESULTS_FILE)) {
let user_input;
while (!['y', 'n'].includes(user_input)) {
user_input = (await promptUser(`❔ ${RESULTS_FILE} file detected. Do you want to download the results now? (y/n): `))?.toLowerCase();
if (user_input == 'y') {
await download(apiToken);
await fs.unlink(RESULTS_FILE);
}
}
}
const start = new Date();
try {
console.info('START EXECUTION', start);
await execute(apiToken, email, promptFile); // Pass the promptFile to execute function
}
finally {
console.info('COMPLETED', new Date());
console.info('EXECUTION ELAPSED', diffInMinutesAndSeconds(start, new Date()));
}
try {
console.info('START DOWNLOAD', start);
await download(apiToken);
}
finally {
console.info('TOTAL ELAPSED', diffInMinutesAndSeconds(start, new Date()));
}
} catch (error) {
console.error('⛔ Error during execution:', error.stack || error);
}
}
// Modify the execute function to accept promptFile as a parameter
async function execute(apiToken, email, promptFile) {
const accounts = await fetchAccounts(apiToken);
console.info(`Configured PixVerse API accounts (${Object.keys(accounts).length}):`, Object.keys(accounts).join(', '));
if (Object.keys(accounts).length <= 0) {
console.error(`⛔ No configured PixVerse accounts found. Please refer to https://useapi.net/docs/start-here/setup-pixverse`);
process.exit(1);
}
if (!accounts[email]) {
console.error(`⛔ Account ${email} not found. Please refer to https://useapi.net/docs/start-here/setup-pixverse`);
process.exit(1);
}
const promptData = await fs.readFile(promptFile, 'utf8');
const prompts = JSON.parse(promptData);
console.log(`Total number of prompts to process`, prompts.length);
let warnings = [];
// Parameters accepted by this script for the POST /videos/create endpoint.
// See https://useapi.net/docs/api-pixverse-v2/post-pixverse-videos-create-v4 for the full parameter set.
const supportedParams = ['model', 'prompt', 'first_frame_path', 'duration', 'quality', 'aspect_ratio',
'audio', 'multi_shot', 'preview_mode', 'off_peak_mode', 'seed', 'template_id'];
const invalidKeys = (prompt) => Object.keys(prompt).filter(key => !key.startsWith('__') && !supportedParams.includes(key))
for (let i = 1; i <= prompts.length; i++) {
const prompt = prompts[i - 1];
const { prompt: text, first_frame_path, template_id } = prompt;
if (first_frame_path) {
try {
await fs.access(first_frame_path);
} catch {
warnings.push(`⚠️ Image '${first_frame_path}' does not exist. Prompt ${i}`);
}
const ext = first_frame_path.split('.').pop().toLowerCase();
if (!supportedFileExtensions.includes(ext))
warnings.push(`⚠️ Image ${first_frame_path} extension ${ext} not supported. Prompt ${i}`);
}
const notSupported = invalidKeys(prompt);
if (notSupported.length)
warnings.push(`⚠️ Following params not supported: ${notSupported.join(',')}. Prompt ${i}`);
// prompt is optional only when a template_id supplies the generation.
if (!text && !template_id)
warnings.push(`⚠️ prompt is required (unless template_id is set). Prompt ${i}`);
}
if (warnings.length > 0) {
warnings.forEach(warning => console.warn(warning));
console.error(`⛔ Execution stopped due to warnings.`);
process.exit(1);
}
for (let i = 0; i < prompts.length; i++) {
const prompt = prompts[i];
while (true) {
const responseCode = await submitVideo(apiToken, email, prompt, i + 1);
if (responseCode == 429)
await sleep(SLEEP_429);
else
if (responseCode == 412) {
process.exit(1);
} else
break;
}
}
}
// Utility function to check if a file exists
async function fileExists(path) {
try {
await fs.access(path);
return true;
} catch {
return false;
}
}
// Function to prompt user input
async function promptUser(query) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve) => rl.question(query, answer => {
rl.close();
resolve(answer);
}));
}
function diffInMinutesAndSeconds(date1, date2) {
const diffInSeconds = Math.floor((date2 - date1) / 1000);
return `${Math.floor(diffInSeconds / 60)} minutes ${diffInSeconds % 60} seconds`;
};
main();
Examples
Real PixVerse clips generated through this PixVerse API, taken straight from our release notes.
v6 » Dramatic courtroom scene (text-to-video, 15s 1080p, multi-shot + audio)
From PixVerse v6: Multi-Shot Storytelling and Audio. Generated with multi_shot: true and audio: true — multiple camera angles and synchronized dialogue in a single 15-second clip.
v5.6 » Playing guitar and singing (image-to-video, integrated audio)
From PixVerse v5.6: Integrated Audio Generation. Animated from a still passed as first_frame_path with audio: true.
v5.6 » Motion Control (retarget motion onto a character image)
From PixVerse: Motion Control. A still character image (frame_1_path) is driven by motion extracted from a reference video (video_1_path) via POST videos/motion-control.
Frequently asked questions
Is there a PixVerse API? Yes — two ways. PixVerse.ai offers an official platform API billed against a separate API subscription. Or use useapi.net’s PixVerse API, which drives your own consumer PixVerse account through a standard REST endpoint. The differentiators: you spend your existing web subscription credits (with the Off-Peak discount, which the official API does not support), you get the full 900+ effect catalog instead of the official API’s 1/3/5 options, and the same single endpoint also exposes third-party models (Seedance, Kling, Veo 3.1, Sora 2, Grok, HappyHorse) that you would otherwise call through separate providers.
What models can I use? The default is native PixVerse V6. The same videos/create endpoint also accepts v5.6, v5.5, v5, v5-fast, and pixverse-c1, plus third-party seedance-2.0 / seedance-2.0-fast, kling-o3 / kling-v3, veo-3.1-lite / veo-3.1-standard / veo-3.1-fast, sora-2 / sora-2-pro, grok-imagine, and happyhorse-1.0. See Supported models and Model Capabilities.
Can I generate audio and multi-shot video? Yes. Set audio: true for integrated voice, dialogue, and background sound (a toggle on v6, v5.6, v5.5, pixverse-c1, Seedance, and Kling; always on for veo-3.1-standard / veo-3.1-fast / happyhorse-1.0). Multi-shot storytelling is v6-only via multi_shot: true. See Audio, multi-shot & effects.
Can I do image-to-video? Yes. Upload your starting frame with POST files, then pass the returned path as first_frame_path on videos/create. For image-to-video, aspect_ratio is derived from the image and must not be set.
How much does it cost? You keep your own PixVerse subscription and add a flat $15/month to useapi.net for API access to all services. Each generation then spends PixVerse credits from your plan — and native models can run far cheaper with off_peak_mode (Pro 30% off, Premium 50% off, Ultra free) or preview_mode (20% off). See Pricing and the interactive calculator.
Do I need PixVerse credits, or are they included? Credits come from your own PixVerse plan — useapi.net does not resell them. The $15/month is purely for API access. A 412 response from videos/create means your PixVerse credits are exhausted.
Conclusion
Visit our Discord Server or
Telegram Channel for any support questions and concerns.
We regularly post guides and tutorials on the YouTube Channel.
Check our GitHub repo with code examples.