Overview
The NanoGPT API provides advanced video generation capabilities using state-of-the-art models. This guide covers how to use our video generation endpoints.
API Authentication
The video generation API supports three authentication methods:
1. Session Authentication (Browser)
- Uses browser cookies automatically
- No additional headers required
2. API Key Authentication
# Using x-api-key header
curl -H "x-api-key: YOUR_API_KEY"
# Using Bearer token
curl -H "Authorization: Bearer YOUR_API_KEY"
Making a Video Generation Request
Endpoint
Content-Type: application/json
x-api-key: YOUR_API_KEY # Optional, for API key auth
Request Body
Basic Text-to-Video Request
{
"model": "veo2-video",
"prompt": "A majestic eagle soaring through mountain peaks at sunset",
"duration": "5s",
"aspect_ratio": "16:9"
}
Image-to-Video Request
{
"model": "kling-v21-standard",
"prompt": "Make the person in the image wave hello",
"imageDataUrl": "data:image/jpeg;base64,/9j/4AAQ...",
"duration": "5",
"aspect_ratio": "16:9"
}
Model-Specific Parameters
Veo Models
{
"model": "veo2-video",
"prompt": "Your prompt",
"duration": "5s", // 5s-30s for Veo2, fixed 8s for Veo3
"aspect_ratio": "16:9" // 16:9, 9:16, 1:1, 4:3, 3:4
}
Kling Models
{
"model": "kling-video-v2",
"prompt": "Your prompt",
"duration": "5", // "5" or "10"
"aspect_ratio": "16:9",
"negative_prompt": "blur, distortion", // Optional
"cfg_scale": 0.5 // 0-1, default 0.5
}
Hunyuan Models
{
"model": "hunyuan-video",
"prompt": "Your prompt",
"pro_mode": false, // true for higher quality (2x cost)
"aspect_ratio": "16:9",
"resolution": "720p", // 480p, 720p, 1080p
"num_frames": 129, // 65, 97, 129
"num_inference_steps": 20, // 10-50
"showExplicitContent": false // Safety filter
}
Wan Image-to-Video
{
"model": "wan-video-image-to-video",
"prompt": "Your prompt",
"imageDataUrl": "data:image/...",
"num_frames": 81, // 81-100
"frames_per_second": 16, // 5-24
"resolution": "720p", // 480p or 720p
"num_inference_steps": 30, // 1-40
"negative_prompt": "blur, distortion",
"seed": 42 // Optional
}
Seedance Models
{
"model": "seedance-video",
"prompt": "Your prompt",
"resolution": "1080p", // 480p or 1080p (standard), 480p or 720p (lite)
"duration": "5", // "5" or "10"
"aspect_ratio": "16:9", // T2V only
"camera_fixed": false, // Static camera
"seed": 42 // Optional
}
Initial Response (202 Accepted)
{
"runId": "fal-request-abc123xyz",
"status": "pending",
"model": "veo2-video",
"cost": 2.5,
"paymentSource": "USD",
"remainingBalance": 47.5
}
Response Fields
runId
: Unique identifier for polling status
status
: Always “pending” for initial response
model
: The model used for generation
cost
: Cost in USD or XNO
paymentSource
: “USD” or “XNO”
remainingBalance
: Account balance after deduction
Polling for Status
After receiving a runId
, poll the status endpoint until completion.
Status Endpoint
GET /api/generate-video/status?runId={runId}&modelSlug={model}
Polling Example
async function pollVideoStatus(runId, model) {
const maxAttempts = 100;
const delayMs = 3000; // 3 seconds
for (let i = 0; i < maxAttempts; i++) {
const response = await fetch(
`/api/generate-video/status?runId=${runId}&modelSlug=${model}`
);
const result = await response.json();
if (result.data.status === 'COMPLETED') {
return result.data.output.video.url;
} else if (result.data.status === 'FAILED') {
throw new Error(result.data.error || 'Video generation failed');
}
// Wait before next poll
await new Promise(resolve => setTimeout(resolve, delayMs));
}
throw new Error('Video generation timed out');
}
Status Response States
In Progress
{
"data": {
"status": "IN_PROGRESS",
"request_id": "fal-request-abc123xyz",
"details": "Video is being generated"
}
}
Completed
{
"data": {
"status": "COMPLETED",
"request_id": "fal-request-abc123xyz",
"output": {
"video": {
"url": "https://storage.example.com/video.mp4"
}
}
}
}
Failed
{
"data": {
"status": "FAILED",
"request_id": "fal-request-abc123xyz",
"error": "Content policy violation",
"isNSFWError": true,
"userFriendlyError": "Content flagged as inappropriate. Please modify your prompt and try again."
}
}
Status Values
IN_QUEUE
: Request is queued
IN_PROGRESS
: Video is being generated
COMPLETED
: Video ready for download
FAILED
: Generation failed
CANCELLED
: Request was cancelled
Complete Examples
Example 1: Text-to-Video with cURL
# 1. Submit video generation request
curl -X POST https://nano-gpt.com/api/generate-video \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"model": "veo2-video",
"prompt": "A beautiful sunset over the ocean with waves",
"duration": "10s",
"aspect_ratio": "16:9"
}'
# Response:
# {
# "runId": "fal-request-abc123",
# "status": "pending",
# "model": "veo2-video",
# "cost": 4.0,
# "paymentSource": "USD"
# }
# 2. Poll for status
curl "https://nano-gpt.com/api/generate-video/status?runId=fal-request-abc123&modelSlug=veo2-video"
# Final response:
# {
# "data": {
# "status": "COMPLETED",
# "output": {
# "video": {
# "url": "https://storage.example.com/video.mp4"
# }
# }
# }
# }
Example 2: Image-to-Video with JavaScript
// 1. Convert image to base64
async function imageToBase64(imageFile) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(imageFile);
});
}
// 2. Submit video generation
async function generateVideo(imageFile) {
const imageDataUrl = await imageToBase64(imageFile);
const response = await fetch('/api/generate-video', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
model: 'kling-v21-pro',
prompt: 'Add gentle camera movement to this scene',
imageDataUrl: imageDataUrl,
duration: '5',
aspect_ratio: '16:9'
})
});
const result = await response.json();
console.log('Video generation started:', result.runId);
// 3. Poll for completion
const videoUrl = await pollVideoStatus(result.runId, result.model);
console.log('Video ready:', videoUrl);
return videoUrl;
}
Example 3: Batch Processing
async function generateMultipleVideos(prompts) {
// Submit all requests
const requests = await Promise.all(
prompts.map(async (prompt) => {
const response = await fetch('/api/generate-video', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify({
model: 'seedance-lite-video',
prompt: prompt,
duration: '5',
resolution: '720p'
})
});
return response.json();
})
);
// Poll all statuses concurrently
const videos = await Promise.all(
requests.map(({ runId, model }) =>
pollVideoStatus(runId, model)
)
);
return videos;
}
Error Handling
Common Error Responses
Insufficient Balance
{
"error": "Insufficient balance",
"status": 402
}
Invalid Session
{
"error": "Invalid session",
"status": 401
}
Rate Limit Exceeded
{
"error": "Rate limit exceeded. Please wait before generating another video.",
"status": 429
}
Content Policy Violation
{
"error": {
"message": "Your prompt was blocked due to safety concerns. Please modify your prompt.",
"type": "CONTENT_POLICY_VIOLATION"
},
"status": 400
}
Model-Specific Errors
{
"error": "Kling 2.1 Standard requires an input image. Please select an image to generate a video.",
"status": 400
}
Error Handling Best Practices
async function generateVideoWithErrorHandling(params) {
try {
// Submit request
const response = await fetch('/api/generate-video', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_API_KEY'
},
body: JSON.stringify(params)
});
if (!response.ok) {
const error = await response.json();
// Handle specific error types
if (response.status === 429) {
console.error('Rate limited, retry after delay');
// Implement exponential backoff
} else if (response.status === 402) {
console.error('Insufficient balance');
// Prompt user to add credits
} else if (error.error?.type === 'CONTENT_POLICY_VIOLATION') {
console.error('Content policy violation');
// Show user-friendly message
}
throw new Error(error.error?.message || error.error);
}
const result = await response.json();
// Poll for status with timeout
const videoUrl = await pollVideoStatus(result.runId, result.model);
return videoUrl;
} catch (error) {
console.error('Video generation failed:', error);
throw error;
}
}
Rate Limits
- Default: 50 requests per minute per IP address
- API Key: Same as default
- Internal Auth: No rate limit
Rate limits apply to the submission endpoint (/api/generate-video
). Status polling endpoints have no rate limits.
Best Practices
-
Choose the Right Model
- Use text-to-video for creative generation
- Use image-to-video for animating existing content
- Consider cost vs quality tradeoffs
-
Optimize Prompts
- Be specific and descriptive
- Include motion and camera directions
- Avoid content policy violations
-
Handle Async Operations
- Implement proper polling with delays
- Set reasonable timeouts (5-10 minutes)
- Show progress to users
-
Error Recovery
- Implement retry logic for transient failures
- Handle rate limits with exponential backoff
- Provide clear error messages to users
-
Cost Management
- Check balance before submitting
- Estimate costs before generation
- Use shorter durations for testing