Fun with MiniMax API
3 min read • September 30, 2024 (October 11, 2024)
Table of contents
Zoom in on a cute red shiny robot holding a white banner with text 'useapi.net' standing in the big data center, in the Pixar animation style.
Introduction
As you probably already figured out, it takes many attempts before you get your perfect 5-second video. This trial and error cycle can be very tedious and time-consuming. But it does not have to be, enter the bright and shiny world of Automation. With a simple Node.js script provided below and some basic file editing, you can cut out all the waiting time and get straight to picking the winners.
In this article, we will show how to use an experimental MiniMax API for MiniMax • Hailuo AI to batch-generate videos.
Preparing prompts
The first step will be to create a file videos.json
as shown below and edit it so it contains desired prompts. You can put as many prompt variations as you can think of. No need to hold back, as you will not be running them manually, not anymore.
It may help to ask ChatGPT, Claude or perhaps Gemini to build prompts for you. This way, you can build a lot of prompts very quickly. Later, you can see what is working and what’s not, and refine them.
Expand videos.json
[
"From below shot of a cat catholic priest performing an exorcism on a demonic cat, a parody on The Exorcist movie.",
"Zoom in on a cute red shiny robot holding a white banner with text 'useapi.net' standing in the big data center, in the Pixar animation style."
]
Executing script
The real magic starts here. We assume that you subscribed to useapi.net. Our subscription is essentially free when you consider the time you will save. It takes only a few minutes to set up MiniMax with our API. We suggest hooking up a dozen or two free Hailuo AI accounts to have enough bandwidth to play with. It usually takes 5-10 minutes of your time max.
🚀 With 15…20 free Hailuo AI accounts configured, you can easily execute approximately 100 generations each hour.
Finally, if you are curious, you can glance over the very detailed documentation we provide. Each endpoint provides the ability to Try It right from the browser. If you don’t feel like reading the docs, that is fine too, the script below is all you need.
We will be using Node.js to execute JavaScript script provided below. Please download and install version 21 or older.
Create a file videos.mjs
with the code provided below.
Your videos.json
should be in the same folder as videos.mjs
.
Finally, execute the script node ./videos.mjs API_TOKEN
where API_TOKEN
is your useapi.net API token.
All generated videos will be downloaded locally to your drive, so you can view them once they are ready.
Expand videos.mjs
/*
Script version 1.0, October 7, 2024
Script to generate videos using prompts with MiniMax API by useapi.net 🚀
For more details visit https://useapi.net/docs/api-minimax-v1
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 commands in a terminal:
node -v
Running the Script:
===================
Usage: node videos.mjs <API_TOKEN> [MAX_AVAILABLE_ACCOUNTS]
- Replace <API_TOKEN> with your actual useapi.net API token. See https://useapi.net/docs/start-here/setup-useapi
- Optionally specify MAX_AVAILABLE_ACCOUNTS (integer) to set a limit on concurrent accounts usage. See https://useapi.net/docs/start-here/setup-minimax
- If MAX_AVAILABLE_ACCOUNTS is not provided, the default value is 1.
Example:
--------
node videos.mjs user:1234-abcdefhijklmnopqrstuv 3
This command executes the script using API token user:1234-abcdefhijklmnopqrstuv and allows up to 3 accounts concurrently.
*/
import readline from 'node:readline';
import fs from 'fs/promises';
import { writeFile } from 'node:fs/promises';
import { Readable } from 'node:stream';
// Constants
const PROMPTS_FILE = 'videos.json';
const RESULTS_FILE = 'videos_results.txt';
const ERRORS_FILE = 'videos_errors.txt';
const SLEEP_429 = 10 * 1000; // in milliseconds
const SLEEP_DOWNLOAD = 20 * 1000; // in milliseconds
// Utility to sleep for given milliseconds
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Function to fetch configured MiniMax API accounts
async function fetchAccounts(apiToken) {
const response = await fetch('https://api.useapi.net/v1/minimax/accounts', {
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();
}
// Function to submit a prompt
async function submitPrompt(apiToken, accounts, prompt, promptIndex, maxAvailableAccounts) {
console.log(`\n👉 Prompt #${promptIndex}: ${prompt}`);
// Fetch running jobs
const runningJobsResponse = await fetch('https://api.useapi.net/v1/minimax/scheduler/', {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`
}
});
const runningJobs = await runningJobsResponse.json();
const runningAccounts = runningJobs.map(job => job.videoId.split('-')[1].split(':')[1]);
console.log(`Executing (busy) accounts: ${runningAccounts.join()}`);
// Determine available accounts
const availableAccounts = accounts.filter(account => !runningAccounts.includes(account));
console.log(`Available accounts: ${availableAccounts.join()}`);
if (availableAccounts.length >= maxAvailableAccounts) {
const results = await Promise.all(
availableAccounts.slice(0, maxAvailableAccounts).map(async (account, i) => {
const ind = i + 1;
const info = `Prompt #${promptIndex} (${ind}) account ${account}`;
console.log(`${info} …`);
const response = await fetch(`https://api.useapi.net/v1/minimax/videos/create`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiToken}`
},
body: JSON.stringify({ account, prompt })
});
const responseBody = await response.text();
if (response.status === 200) {
const json = JSON.parse(responseBody);
const videoId = json.videoId;
if (videoId) {
await fs.appendFile(RESULTS_FILE, `${videoId},${prompt}\n`);
return 200;
} else {
console.log(`❓ ${info}: No videoId found in HTTP 200 response`, responseBody);
return null;
}
} else {
switch (response.status) {
case 422:
console.log(`🛑 ${info}: MODERATED prompt`, responseBody);
break;
case 429:
console.log(`🔄️ ${info}: retry on HTTP ${response.status}`);
break;
default:
console.log(`❗ ${info}: FAILED with HTTP ${response.status}`, responseBody);
}
return response.status;
}
})
);
console.log(`Results array: ${results.join(', ')}`);
if (results.includes(200)) {
console.log(`✅ Prompt #${promptIndex} completed`);
} else if (results.every(status => status === 429)) {
console.log(`🔄️ All responses are 429, retrying prompt #${promptIndex}…`);
await sleep(SLEEP_429);
await submitPrompt(apiToken, accounts, prompt, promptIndex, maxAvailableAccounts);
} else {
console.log(`🛑 MODERATED prompt (HTTP ${results.join(' ')}): ${prompt}`);
await fs.appendFile(ERRORS_FILE, `${results.join(' ')},${prompt}\n`);
}
} else {
console.log(`⌛ Not enough available accounts, waiting and retrying…`);
await sleep(SLEEP_429);
await submitPrompt(apiToken, accounts, prompt, promptIndex, maxAvailableAccounts);
}
}
// Function to execute the script
async function execute(apiToken, maxAvailableAccounts) {
const accountsData = await fetchAccounts(apiToken);
const accounts = Object.keys(accountsData)
// Added October 11, 2024 to support https://hailuoai.video
.filter(account => account.supportVideo);
console.info(`Configured MiniMax API https://hailuoai.video accounts (${accounts.length}):`, accounts.join());
if (accounts.length < maxAvailableAccounts) {
console.error(`⛔ Number of configured https://hailuoai.video accounts (${accounts.length}) is less than required (${maxAvailableAccounts}).`);
process.exit(1);
}
const promptData = await fs.readFile(PROMPTS_FILE, 'utf8');
const prompts = JSON.parse(promptData);
console.log(`Total number of prompts to process: ${prompts.length}`);
for (let i = 0; i < prompts.length; i++) {
await submitPrompt(apiToken, accounts, prompts[i], i, maxAvailableAccounts);
}
}
// Function to download videos based on VIDEO IDs
async function download(apiToken) {
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(',');
const videoFilename = `${videoId.replace(/:/g, '_')}.mp4`;
console.log(`👉 ${videoId}`);
try {
await fs.access(videoFilename);
console.log(`⚠️ ${videoFilename} already exists. Skipping download.`);
continue;
} catch {
// File does not exist, proceed with downloading
}
while (true) {
const response = await fetch(`https://api.useapi.net/v1/minimax/videos/${videoId}`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`
}
});
if (!response.ok) {
console.log(`🛑 MODERATED ${videoId} (HTTP ${response.status}):\n${prompt}\n`, await response.text());
break;
}
const taskResponseBody = await response.json();
const { status, videoURL, downloadURL, percent } = taskResponseBody;
if (status === 2 && (videoURL || downloadURL)) {
// downloadURL for paid accounts only
const actualUrl = downloadURL ?? videoURL;
console.log(`✅ Downloading ${actualUrl} to ${videoFilename}`);
try {
const videoResponse = await fetch(actualUrl);
if (!videoResponse.ok) {
console.error(`⛔ Unable to download ${videoId} (HTTP ${videoResponse.status}):\n${prompt}\n`, actualUrl);
break;
}
const stream = Readable.fromWeb(videoResponse.body);
await writeFile(videoFilename, stream);
} catch (err) {
console.error(`⛔ Error during download: ${err}`);
}
break;
} else {
console.log(`⌛ ${videoId} status (${status}) and is still in progress (${percent}%), waiting…`);
await sleep(SLEEP_DOWNLOAD);
}
}
}
} catch (error) {
console.log(`⛔ Error during download:`, error.stack || error);
}
}
// Main function
async function main() {
const apiToken = process.argv[2];
if (!apiToken) {
console.error('Usage: node videos.mjs <API_TOKEN> [MAX_AVAILABLE_ACCOUNTS]');
process.exit(1);
}
// Parse and validate max available accounts
const maxAvailableAccounts = parseInt(process.argv[3], 10);
const maxAccounts = isNaN(maxAvailableAccounts) ? 1 : maxAvailableAccounts;
console.log('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);
}
}
}
await execute(apiToken, maxAccounts);
await download(apiToken);
} catch (error) {
console.error('⛔ Error during execution:', error.stack || error);
}
}
// 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);
}));
}
main();
Examples
From below shot of a cat catholic priest performing an exorcism on a demonic cat, a parody on The Exorcist movie.
Surreal fantasy world: light pink sky, ocean made entirely of glowing sparkling water, focus on Daenerys Targaryen quickly raising from the ocean, as she looks straight at the camera. Her skin is smooth and moist, she has shoulder-long platinum blonde wet hair, her tummy is toned. Eyes deep green color almost glowing. Sparkling liquid surrounding her because surreal ocean is composed of glowing sparkling water.
Camera zooming slowly on a very pretty lady wearing pink bikini, she is jogging on the beach.
Hungry snake hunting the mouse.
From below tracking shot of a tall man from his back walking down a long symmetrical alley with trees on both sides. He is wearing a long bright blue colored trench coat. The trees are magnificent boasting wide branches filled with abundant yellow leaves. It is windy so the man’s coat and leaves on the trees are billowing, moving.
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.