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.
Check our GitHub repo with code examples.