Como usar a API do Veo 3.1: gerar vídeo com curl pela Google Flow API
6 min de leitura • 15 de junho de 2026 (6 de novembro de 2026)
A useapi.net é uma API de terceiros. Não tem afiliação com o Google nem é endossada por ele.
Sumário
- Introdução
- Modelos suportados
- Preços
- Gere um vídeo em duas chamadas de API
- Imagem para vídeo e referência para vídeo
- Gere em lote com um script
- Exemplos
- Perguntas frequentes
- Conclusão
Introdução
O Veo 3.1 roda com um único curl pela Google Flow API da useapi.net, na sua assinatura do site Google Flow (Google AI Pro ou Ultra). Um vídeo Veo 3.1 Fast de 8 segundos custa $0,08 no Google AI Ultra $199,99, contra $0,80 na API oficial do Veo (em novembro de 2026).
O Google desligou os modelos Veo 3.1 Preview na Gemini API em 22 de outubro de 2026 e levou os desenvolvedores para a Agent Platform do Cloud, que cobra os mesmos preços por segundo em um projeto Google Cloud. Esta API de terceiros não precisa de projeto no Cloud, nem de chave de API do Google, nem de aprovação enterprise. A seguir: os modelos, o fluxo em duas chamadas, entradas de imagem e um script de lote pronto para rodar.
Modelos suportados
Escolha o modelo em cada requisição com o campo model (padrão veo-3.1-fast). Os custos em créditos do Veo vêm da tabela oficial de créditos do Flow do Google, e os do Omni foram medidos na API ao vivo:
| Id do modelo | Nível / velocidade | Durações | Créditos por vídeo |
|---|---|---|---|
veo-3.1-lite | Veo mais barato | 4s, 6s, 8s | 10 (fora do Ultra) · 5 (Ultra) |
veo-3.1-lite-low-priority | Lite, prioridade baixa | 4s, 6s, 8s | 0, só o titular do Ultra $199,99 |
veo-3.1-fast (padrão) | Veo rápido | 4s, 6s, 8s | 20 (fora do Ultra) · 10 (Ultra) |
veo-3.1-quality | Veo de mais qualidade | só 8s | 100 |
omni-flash-1.2 | Omni 1.2 Flash, áudio nativo | 3s a 15s | 3 por segundo até 10s, 6 por segundo acima (em 720p) |
omni-flash | Omni 1.1 Flash, áudio nativo | 4s, 6s, 8s, 10s | 7 / 10 / 12 / 15 (pela duração, em 720p) |
No Veo, vídeos de 4s e 6s exigem o Ultra (veja a visão geral). O Google Flow também gera imagens com os modelos Nano Banana pelo endpoint separado POST /images.
Preços
Cada vídeo gasta créditos do Flow da sua assinatura do Google AI, e a useapi.net não cobra nada por vídeo. O preço por vídeo em cada plano, comparado com a API oficial, está na tabela de preços do Google Flow, e o artigo Veo 3.1 API pricing (em inglês) cobre o Veo em detalhe.
Gere um vídeo em duas chamadas de API
Você precisa de um token da API da useapi.net e de uma conta Google Flow conectada. Jobs de vídeo são assíncronos: passe async: true e a chamada de criação devolve um jobid na hora, depois você consulta até o vídeo ficar pronto.
- Envie o job —
POST https://api.useapi.net/v1/google-flow/videos:
curl -X POST "https://api.useapi.net/v1/google-flow/videos" \
-H "Authorization: Bearer $USEAPI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A serene mountain landscape at sunset, camera slowly panning right",
"model": "veo-3.1-fast",
"aspectRatio": "landscape",
"duration": 8,
"async": true
}'
No modo assíncrono, a resposta volta na hora com 201 Created, um jobid em minúsculas e status: "created":
{
"jobid": "j1731859234567v-u12345-email:jo***@gmail.com-bot:google-flow",
"type": "video",
"status": "created",
"created": "2026-06-15T12:34:56.789Z",
"request": {
"async": true,
"prompt": "A serene mountain landscape at sunset, camera slowly panning right",
"model": "veo-3.1-fast",
"aspectRatio": "landscape",
"duration": 8
}
}
- Consulte o resultado —
GET https://api.useapi.net/v1/google-flow/jobs/{jobId}:
curl "https://api.useapi.net/v1/google-flow/jobs/{jobId}" \
-H "Authorization: Bearer $USEAPI_TOKEN"
O status passa por created → started → completed. Quando chega a completed, o MP4 pronto está em response.media[].videoUrl (um único prompt com count > 1 devolve uma entrada por vídeo):
{
"jobid": "j1731859234567v-...",
"type": "video",
"status": "completed",
"response": {
"media": [
{
"mediaGenerationId": "user:12345-email:6a6f...-video:a1d95d21-...",
"videoUrl": "https://flow-content.google/video/a1d95d21-...?Expires=...",
"thumbnailUrl": "https://flow-content.google/image/a1d95d21-...?Expires=..."
}
],
"remainingCredits": 18760
}
}
O Veo 3.1 costuma terminar em 60 a 180 segundos, conforme o modelo e o modo. Nos nossos testes de 20 de outubro de 2026, nove vídeos de 8 segundos levaram de 50 a 105 segundos do envio até o MP4 pronto (execuções medidas). O videoUrl assinado vale por cerca de 6 horas, então baixe logo. Prefere não consultar? Passe um replyUrl no corpo da criação para receber um callback por webhook quando o job terminar.
Imagem para vídeo e referência para vídeo
O Veo aceita imagens de entrada de dois jeitos diferentes. Envie antes cada imagem com POST /assets/email (bytes brutos, um Content-Type de imagem, PNG/JPEG/WebP de até 20 MB). A resposta do upload traz o id aninhado em mediaGenerationId.mediaGenerationId — é essa string aninhada que você passa de volta. Quando você manda qualquer imagem de entrada, pode omitir o email: a requisição vai para a conta em que a imagem foi enviada.
Primeiro / último quadro (I2V)
A sua imagem vira um quadro literal do vídeo. Passe a imagem como startImage para começar a partir desse quadro:
curl -X POST "https://api.useapi.net/v1/google-flow/videos" \
-H "Authorization: Bearer $USEAPI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Brave Captain Cat chasing away the pirates!",
"model": "veo-3.1-fast",
"aspectRatio": "portrait",
"startImage": "user:12345-email:6a6f...-image:ff9aa5cc-...",
"async": true
}'
Acrescente endImage (um segundo id de referência enviado) para fixar o último quadro, e o Veo gera a transição entre os dois. endImage exige startImage — só o quadro final não é suportado. Os modelos Omni (omni-flash-1.2, omni-flash) aceitam o mesmo par, mas ali os dois quadros são a única entrada aceita — nada de referenceImage_*, character_* ou referenceAudio_* junto com eles.
Imagens de referência (R2V)
As suas imagens guiam o vídeo inteiro — tema, personagem ou estilo — em vez de um quadro específico. Passe de referenceImage_1 a referenceImage_3 (o Veo aceita até 3, só em vídeos de 8 segundos, e não no veo-3.1-quality). R2V não pode ser combinado com startImage/endImage:
curl -X POST "https://api.useapi.net/v1/google-flow/videos" \
-H "Authorization: Bearer $USEAPI_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"prompt": "A lady says to the dog, \"Say meow meow meow.\" The dog looks at the lady with a surprised, frustrated look and woofs in a human voice, \"What the heck lady?!\"",
"model": "veo-3.1-fast",
"aspectRatio": "portrait",
"referenceImage_1": "user:12345-email:6a6f...-image:abcde-...",
"async": true
}'
Mencione cada referência inline com um marcador @referenceImage_N no prompt, acrescente um referenceAudio_1 (o nome de uma voz do sistema, como Zephyr) para narração, ou passe um personagem reutilizável como character_1 (ele usa o mesmo modo R2V e conta no limite de 3 referências). O Omni Flash vai além com as referências — até 7, mais edição de vídeo para vídeo e vozes sincronizadas — veja o tutorial do Omni Flash (em inglês).
Gere em lote com um script
Achar a tomada certa leva muitas tentativas, e rodar tudo à mão é cansativo. O script Node.js abaixo lê uma lista de prompts de prompts.json, envia as imagens de quadro inicial/final, manda cada job no modo assíncrono, depois consulta e baixa cada MP4 pronto — assim você enfileira um lote e volta para pegar os melhores.
Você precisa do Node.js v21 ou mais novo. Coloque prompts.json e google-flow.mjs na mesma pasta e rode node ./google-flow.mjs API_TOKEN EMAIL, em que API_TOKEN é o seu token da API da useapi.net e EMAIL é o e-mail da sua conta Google Flow conectada. O script busca a conta pelo e-mail automaticamente e confere o campo health dela antes de enviar.
Prefere clonar e rodar localmente? O google-flow.mjs e o prompts.json completos estão no GitHub em useapi/google-flow-api.
Abrir prompts.json
[
{
"prompt": "By default the veo-3.1-fast model with a landscape 8-second clip will be used."
},
{
"model": "veo-3.1-quality",
"aspectRatio": "landscape",
"duration": 8,
"count": 2,
"seed": 123456,
"prompt": "Highest-quality Veo 3.1, two variations. For all parameters see https://useapi.net/docs/api-google-flow-v1/post-google-flow-videos"
},
{
"model": "veo-3.1-fast",
"aspectRatio": "portrait",
"duration": 8,
"startImage": "./first_image.jpeg",
"endImage": "./last_image.jpeg",
"prompt": "Image-to-video with a start frame (startImage) and an end frame (endImage). Supported by Veo and by both Omni models."
},
{
"model": "omni-flash",
"duration": 10,
"prompt": "Gemini Omni Flash is audio-native and supports 10-second clips. Other models (veo-3.1-lite, veo-3.1-lite-low-priority, …) are selectable via the model field — see the POST /videos docs."
}
]
Abrir o script google-flow.mjs
/*
Script version 1.1, September 11, 2026
Script to batch-generate videos using prompts with the Google Flow API v1 by useapi.net 🚀
Uses the POST /videos endpoint in async mode (default model: veo-3.1-fast) and polls GET /jobs/{jobId}.
For more details visit https://useapi.net/docs/api-google-flow-v1/post-google-flow-videos
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 google-flow.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 Google Flow email account, see https://useapi.net/docs/start-here/setup-google-flow
If optional PROMPTS_FILE not provided prompts.json will be used.
Example:
--------
node google-flow.mjs user:1234-abcdefhijklmnopqrstuv [email protected]
This command executes the script using API token user:1234-abcdefhijklmnopqrstuv with [email protected] Google Flow account email.
Changelog:
==========
- June 15, 2026: Initial release.
- September 11, 2026: Accepts .jpg and upper-case image extensions for startImage and endImage. A prompt whose startImage or endImage fails to upload is skipped and logged to google-flow_errors.txt instead of being submitted without that frame. Saves a downloaded video under its final name only once it has been fully received, so a failed download leaves no partial MP4 for the next run to skip as "already exists".
*/
import readline from 'node:readline';
import fs from 'fs/promises';
import path from 'path';
import { writeFile } from 'node:fs/promises';
import { Readable } from 'node:stream';
// Constants
const RESULTS_FILE = 'google-flow_results.txt';
const ERRORS_FILE = 'google-flow_errors.txt';
const DEFAULT_PROMPTS_FILE = 'prompts.json';
const DEFAULT_MODEL = 'veo-3.1-fast';
const SLEEP_429 = 30 * 1000; // in milliseconds
const SLEEP_POLL = 15 * 1000; // in milliseconds
const urlAccounts = 'https://api.useapi.net/v1/google-flow/accounts';
const urlVideos = 'https://api.useapi.net/v1/google-flow/videos';
const urlJobs = 'https://api.useapi.net/v1/google-flow/jobs/';
const urlUploadAsset = 'https://api.useapi.net/v1/google-flow/assets/';
// To upload .webp keep its .webp extension — Google Flow accepts png, jpeg and webp.
const supportedFileExtensions = ['png', 'jpg', 'jpeg', 'webp'];
// { filename: mediaGenerationId }
const uploadedFiles = {};
// Utility to sleep for given milliseconds
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Function to fetch configured Google Flow 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 /assets
const contentTypeForExt = (ext) => ext === 'png' ? 'image/png' : ext === 'webp' ? 'image/webp' : 'image/jpeg';
async function uploadAsset(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(`${urlUploadAsset}${encodeURIComponent(email)}`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`,
'Content-Type': contentTypeForExt(fileExt)
},
body
});
if (response.ok) {
const json = await response.json();
// POST /assets returns the reference id nested as mediaGenerationId.mediaGenerationId
const mediaGenerationId = json?.mediaGenerationId?.mediaGenerationId;
console.log(`🆗 mediaGenerationId (${elapsedTimeSec(startTime)} sec)`, mediaGenerationId);
uploadedFiles[filename] = mediaGenerationId;
}
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();
// Async POST /videos returns 201 Created with a jobid to poll.
if (createResponse.status == 201) {
const json = JSON.parse(createBody);
// 201 async payload uses lowercase "jobid"; sync 200 uses "jobId". Accept either.
const jobId = json.jobid ?? json.jobId;
if (jobId) {
await fs.appendFile(RESULTS_FILE, `${jobId},#${index}:${prompt}\n`);
console.log(`✅ jobId`, jobId);
return 201;
} else {
const error = `No jobid found in HTTP 201 response`;
console.log(`❓ ${error}`, createBody);
await fs.appendFile(ERRORS_FILE, `${error},#${index}:${prompt}\n`);
return 500;
}
} else {
switch (createResponse.status) {
case 429:
console.log(`🔄️ Retry on HTTP ${createResponse.status}`, createBody);
break;
case 503:
console.log(`🔄️ Service unavailable, retry on HTTP ${createResponse.status}`, createBody);
break;
case 400:
console.log(`🛑 Rejected request (validation or content policy)`, createBody);
await fs.appendFile(ERRORS_FILE, `${createResponse.status},#${index}:${prompt}\n`);
break;
case 402:
console.log(`🛑 No subscription / insufficient credits`, createBody);
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 in async mode.
// startImage = start frame (I2V), endImage = end frame (I2V-FL, requires startImage).
async function submitVideo(apiToken, email, prompt, index) {
const { model, prompt: text, startImage, endImage, aspectRatio, duration, count, seed } = prompt;
const useModel = model ?? DEFAULT_MODEL;
console.log(`🚀 ${useModel} » Prompt #${index} • account ${email} …`);
const startImageId = startImage ? await uploadAsset(apiToken, email, startImage) : undefined;
const endImageId = endImage ? await uploadAsset(apiToken, email, endImage) : undefined;
// A frame that failed to upload would be left out of the body, and the prompt would still be
// generated (and charged) without it. Skip the prompt instead. uploadAsset remembers the failed file,
// so a later prompt using it is skipped too, without another upload attempt.
const failedUploads = [['startImage', startImage, startImageId], ['endImage', endImage, endImageId]]
.filter(([, file, id]) => file && !id);
for (const [param, file] of failedUploads) {
console.error(`🛑 Prompt #${index} skipped: ${param} ${file} failed to upload`);
await fs.appendFile(ERRORS_FILE, `Upload failed for ${param} ${file},#${index}:${text}\n`);
}
if (failedUploads.length)
return;
const body = JSON.stringify({
model: useModel,
email,
prompt: text,
aspectRatio,
duration,
count,
seed,
startImage: startImageId,
endImage: endImageId,
async: true
});
return await submit(apiToken, urlVideos, body, index, text);
}
// Function to 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 [jobId, prompt] = line.split(',');
console.log(`👉 ${jobId}`);
while (true) {
const response = await fetch(`${urlJobs}${jobId}`, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${apiToken}`
}
});
if (!response.ok) {
console.log(`🛑 Poll failed ${jobId} (HTTP ${response.status}):\n${prompt}\n`, await response.text());
break;
}
const jobBody = await response.json();
const { status, error, response: jobResponse } = jobBody;
if (status == 'failed') {
console.error(`🛑 FAILED ${jobId} (${error}):\n${prompt}\n`);
break;
}
if (status == 'completed') {
const media = jobResponse?.media ?? [];
if (media.length == 0)
console.error(`🛑 Completed but no media for ${jobId}:\n${prompt}\n`);
// A single prompt with count > 1 returns multiple videos.
for (let i = 0; i < media.length; i++) {
const url = media[i]?.videoUrl;
const videoFilename = `${jobId.replace(/[:*]/g, '_')}_${i + 1}.mp4`;
try {
await fs.access(videoFilename);
console.log(`⚠️ ${videoFilename} already exists. Skipping download.`);
continue;
} catch {
// File does not exist, proceed with downloading
}
if (url) {
console.log(`✅ Downloading ${url} to ${videoFilename}`);
// Stream into a temporary file and rename it once the whole video has arrived, so a download
// that drops mid-stream leaves no partial file behind for the next run to skip as "already exists".
const tempFilename = `${videoFilename}.part`;
try {
const videoResponse = await fetch(url);
if (!videoResponse.ok) {
console.error(`⛔ Unable to download ${jobId} (HTTP ${videoResponse.status}):\n${prompt}\n`, url);
continue;
}
const stream = Readable.fromWeb(videoResponse.body);
await writeFile(tempFilename, stream);
await fs.rename(tempFilename, videoFilename);
} catch (err) {
await fs.rm(tempFilename, { force: true });
console.error(`⛔ Error during download: ${err}`);
}
} else
console.error(`🛑 No videoUrl for ${jobId} media #${i + 1}:\n${prompt}\n`);
}
break;
}
console.log(`⌛ ${jobId} status (${status}) and is still in progress, waiting…`);
await sleep(SLEEP_POLL);
}
}
} 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 google-flow.mjs <API_TOKEN> <EMAIL> [PROMPTS_FILE]');
process.exit(1);
}
console.info('Script v1.1');
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 Google Flow API accounts (${Object.keys(accounts).length}):`, Object.keys(accounts).join(', '));
if (Object.keys(accounts).length <= 0) {
console.error(`⛔ No configured Google Flow accounts found. Please refer to https://useapi.net/docs/start-here/setup-google-flow`);
process.exit(1);
}
if (!accounts[email]) {
console.error(`⛔ Account ${email} not found. Please refer to https://useapi.net/docs/start-here/setup-google-flow`);
process.exit(1);
}
if (accounts[email].health && accounts[email].health !== 'OK') {
console.error(`⛔ Account ${email} health is '${accounts[email].health}'. Please resolve and update the account, see https://useapi.net/docs/start-here/setup-google-flow`);
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 endpoint.
// See https://useapi.net/docs/api-google-flow-v1/post-google-flow-videos for every model's full parameter set.
const supportedParams = ['model', 'prompt', 'startImage', 'endImage', 'aspectRatio', 'duration', 'count', 'seed'];
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, startImage, endImage } = prompt;
const validateImage = async (file) => {
if (file) {
try {
await fs.access(file);
} catch {
warnings.push(`⚠️ Image '${file}' does not exist. Prompt ${i}`);
}
const ext = file.split('.').pop().toLowerCase();
if (!supportedFileExtensions.includes(ext))
warnings.push(`⚠️ Image ${file} extension ${ext} not supported. Prompt ${i}`);
}
};
const notSupported = invalidKeys(prompt);
if (notSupported.length)
warnings.push(`⚠️ Following params not supported: ${notSupported.join(',')}. Prompt ${i}`);
if (!text)
warnings.push(`⚠️ prompt is required. Prompt ${i}`);
if (endImage && !startImage)
warnings.push(`⚠️ endImage requires startImage (end-frame-only is not supported). Prompt ${i}`);
await Promise.all([validateImage(startImage), validateImage(endImage)]);
}
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 || responseCode == 503)
await sleep(SLEEP_429);
else
if (responseCode == 402) {
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();
Exemplos
Vídeos Veo 3.1 reais feitos por esta Google Flow API, tirados direto das nossas notas de lançamento.
veo-3.1-fast » Capitão Gato corajoso espantando os piratas (imagem para vídeo)
De Google Flow API v1: Initial Release (em inglês). Animado a partir de uma imagem Nano Banana passada como startImage.
veo-3.1-fast » Uma mulher e o seu cachorro, com narração por voz de IA
De Veo 3.1 Lite and Voice Narration (em inglês). Referência para vídeo com o preset referenceAudio_1: "Zephyr" acrescentando diálogo falado.
veo-3.1-fast » Dançando chá-chá-chá (referência para vídeo em retrato)
De Veo 3.1 Portrait R2V (em inglês). R2V em retrato a partir de uma única imagem de referência.
Perguntas frequentes
- Existe API do Veo 3.1? Com a useapi.net, sim: a Google Flow API roda o Veo 3.1 Quality, Fast e Lite por REST na sua assinatura do site Google Flow (Google AI Pro ou Ultra). Um vídeo Veo 3.1 Quality de 8 segundos custa $0,80 no Google AI Ultra $199,99, contra $3,20 na API oficial do Veo (em novembro de 2026).
- Qual a diferença entre Veo 3.1 Quality, Fast e Lite? Com a useapi.net você escolhe um por requisição com o campo
model. Oveo-3.1-qualitydá a maior fidelidade, oveo-3.1-fasté o equilíbrio padrão entre velocidade e qualidade, e oveo-3.1-liteé o mais barato: $0,04 por vídeo no Google AI Ultra $199,99, contra $0,40 na API oficial do Veo. Nesse plano, oveo-3.1-lite-low-prioritycusta $0, cerca de 1.000 vídeos ou mais por dia (em novembro de 2026). - Dá para fazer imagem para vídeo com o Veo? Com a useapi.net, sim. Envie o quadro inicial com POST /assets/
email, depois passe o id de referência devolvido (aninhado emmediaGenerationId.mediaGenerationId) comostartImagee, se quiser,endImagepara fixar o último quadro (ele exigestartImage). Para guiar o tema ou o estilo em vez de um quadro, passe até trêsreferenceImage_N. Veja Imagem para vídeo acima. - Preciso resolver captcha? Com a useapi.net você nunca resolve um captcha. O Google Flow pede um reCAPTCHA em cada vídeo, e o worker da useapi.net resolve automaticamente: a sua primeira conta Google Flow vem com 300 resoluções grátis nas nossas próprias chaves de AntiCaptcha, CapSolver e EzCaptcha, e depois disso você acrescenta as chaves do seu próprio provedor com POST /accounts/captcha-providers. O script deste guia não envia
captchaToken. - Preciso de uma chave da API do Veo 3.1? Com a useapi.net você não precisa de chave de API do Google nem de projeto no Google Cloud: um único token da API da useapi.net chama o Veo 3.1, e você conecta a sua conta Google uma vez pela configuração automática. O Google Flow em si não tem API pública, e desde que os modelos Veo 3.1 Preview da Gemini API foram desligados em 22 de outubro de 2026, a API do Veo do próprio Google exige um projeto no Cloud com faturamento.
Conclusão
Visite o nosso servidor no Discord ou o
canal no Telegram para qualquer dúvida de suporte.
Veja o nosso repositório no GitHub com exemplos de código.
Publicado também em
Também publicado no dev.to (em inglês).
Comece a usar a API
Uma assinatura de US$ 15 por mês dá acesso a todas as APIs da useapi.net: um token, todos os serviços. Você conecta até 3 contas suas por serviço, e a useapi.net cuida da API REST, do balanceamento de carga e do polling. Precisa de mais contas? Cada assinatura extra custa US$ 10 por mês e acrescenta mais 3 contas por serviço.
Não tem cartão de crédito internacional? Você também pode assinar com criptomoedas (pagamentos em cripto não são reembolsáveis). Preços e planos de volume estão na página de assinatura. Pagando pelo Stripe, você recebe reembolso integral em até 14 dias após a primeira compra, se tiver menos de 50 gerações bem-sucedidas. Já é assinante? Altere a quantidade no portal do cliente Stripe em vez de comprar de novo.