Installation
Copy
npm install @stephen_turtles/bluma-sdk
- Node.js 18+ or Bun 1.0+
- TypeScript 5.0+ (for TypeScript projects)
Quick Start
Copy
import { Bluma } from '@stephen_turtles/bluma-sdk';
const bluma = new Bluma({
apiKey: process.env.BLUMA_API_KEY
});
// Generate a video
const video = await bluma.videos.create({
templateId: 'consumerclub-discord-zoomed',
context: {
prompt: 'Create a fun personality quiz about food'
}
});
console.log('Video created:', video.id);
// Wait for completion
const completed = await bluma.videos.waitFor(video.id);
console.log('Video ready:', completed.url);
Configuration
Basic Configuration
Copy
import { Bluma } from '@stephen_turtles/bluma-sdk';
const bluma = new Bluma({
apiKey: process.env.BLUMA_API_KEY,
baseUrl: 'https://api.getbluma.com/api/v1', // Optional, default shown
timeout: 30000, // 30 seconds
maxRetries: 3,
retryDelay: 1000 // milliseconds
});
Environment-Based Configuration
Copy
const config = {
development: {
apiKey: process.env.BLUMA_TEST_KEY,
timeout: 60000 // Longer timeout for development
},
production: {
apiKey: process.env.BLUMA_LIVE_KEY,
timeout: 30000
}
};
const env = process.env.NODE_ENV || 'development';
const bluma = new Bluma(config[env]);
Videos API
Create a Video
Copy
// Using a template directly
const video = await bluma.videos.create({
templateId: 'consumerclub-discord-zoomed',
context: {
prompt: 'Create a fun personality quiz about your favorite foods'
}
});
// Using a variant preset (saved configuration)
const video = await bluma.videos.create({
variantId: 'var_xyz789', // Uses pre-configured settings
context: {
prompt: 'Create a fun personality quiz'
}
});
// With all optional parameters
const video = await bluma.videos.create({
templateId: 'ugc-text-overlay',
context: {
prompt: 'Showcase my new product',
brandAssets: { logo: 'https://cdn.example.com/logo.png' },
systemPrompt: 'You are an enthusiastic marketer'
},
options: {
resolution: '4k', // '720p', '1080p', or '4k'
watermark: false // Add watermark (test keys only)
},
webhookUrl: 'https://myapp.com/webhooks/bluma'
});
console.log(video.id); // 'batch_abc123xyz'
console.log(video.status); // 'queued'
console.log(video.templateId); // 'ugc-text-overlay'
console.log(video.variantId); // 'var_xyz789' or undefined
console.log(video.creditsCharged); // 6
Copy
interface VideoContext {
prompt: string;
brandAssets?: Record<string, any>;
systemPrompt?: string;
}
interface VideoOptions {
resolution?: '720p' | '1080p' | '4k';
watermark?: boolean;
}
interface VideoCreateParams {
/** Either templateId or variantId must be provided, but not both */
templateId?: string;
variantId?: string;
context: VideoContext;
options?: VideoOptions;
webhookUrl?: string;
}
interface Video {
id: string;
status: VideoStatus;
templateId: string;
variantId?: string;
url?: string;
thumbnailUrl?: string;
duration?: number;
sizeBytes?: number;
creditsCharged: number;
createdAt: Date;
completedAt?: Date;
error?: {
type: string;
detail: string;
};
}
enum VideoStatus {
Queued = 'queued',
Processing = 'processing',
Completed = 'completed',
Failed = 'failed'
}
Get Video Status
Copy
const video = await bluma.videos.get('batch_abc123xyz');
console.log(video.status); // 'completed'
console.log(video.url); // 'https://cdn.getbluma.com/videos/...'
console.log(video.duration); // 45 (seconds)
Wait for Video Completion
Copy
// Polls until video is completed or failed
const video = await bluma.videos.waitFor('batch_abc123xyz', {
pollInterval: 5000, // Check every 5 seconds (default)
timeout: 300000 // Timeout after 5 minutes (default: 10 minutes)
});
if (video.status === 'completed') {
console.log('Video ready:', video.url);
} else {
console.error('Video failed:', video.error);
}
Download Video
Copy
const download = await bluma.videos.download('batch_abc123xyz');
console.log(download.downloadUrl); // Presigned URL
console.log(download.expiresAt); // Expiration timestamp
// Download to file
import fs from 'fs';
import { pipeline } from 'stream/promises';
const response = await fetch(download.downloadUrl);
await pipeline(
response.body,
fs.createWriteStream('my-video.mp4')
);
Templates API
List All Templates
Copy
const templates = await bluma.templates.list();
templates.forEach(template => {
console.log(template.id, template.name);
console.log('Base cost:', template.baseCost, 'credits');
});
Copy
interface Template {
id: string;
name: string;
description: string;
baseCost: number;
category: string;
duration: number;
aspectRatio: string;
contextSchema: Record<string, any>;
exampleUrl?: string;
}
Get Template Details
Copy
const template = await bluma.templates.get('consumerclub-discord-zoomed');
console.log(template.name); // 'ConsumerClub Discord Zoomed'
console.log(template.description); // 'Create personality quiz videos...'
console.log(template.contextSchema); // JSON schema for context
Credits API
Get Credit Balance
Copy
const balance = await bluma.credits.getBalance();
console.log(balance.credits); // 88
console.log(balance.tier); // 'pro'
console.log(balance.monthlyAllowance); // 500
console.log(balance.resetDate); // Date object
Get Credit History
Copy
const history = await bluma.credits.getHistory({
limit: 50,
offset: 0
});
history.transactions.forEach(txn => {
console.log(txn.type); // 'deduction' | 'purchase'
console.log(txn.amount); // -6 or +100
console.log(txn.description); // 'Video generation: batch_xyz789'
console.log(txn.createdAt); // Date object
});
console.log(history.total); // Total transaction count
Webhooks
Verify Webhook Signature
Copy
import express from 'express';
const app = express();
app.post('/webhooks/bluma',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-bluma-signature'] as string;
const payload = req.body.toString();
try {
// Option 1: Named import
import { verifyWebhook } from '@stephen_turtles/bluma-sdk';
const event = verifyWebhook(
payload,
signature,
process.env.BLUMA_WEBHOOK_SECRET!
);
// Option 2: Webhooks object
import { webhooks } from '@stephen_turtles/bluma-sdk';
const event = webhooks.verify(
payload,
signature,
process.env.BLUMA_WEBHOOK_SECRET!
);
// Handle event
switch (event.type) {
case 'video.completed':
console.log('Video ready:', event.data.url);
break;
case 'video.failed':
console.error('Video failed:', event.data.error);
break;
case 'credits.low':
console.warn('Credits low:', event.data.remaining);
break;
}
res.sendStatus(200);
} catch (error) {
console.error('Invalid webhook signature');
res.status(401).send('Unauthorized');
}
}
);
Copy
type WebhookEvent =
| VideoQueuedEvent
| VideoProcessingEvent
| VideoCompletedEvent
| VideoFailedEvent
| CreditsLowEvent
| CreditsExhaustedEvent;
interface VideoCompletedEvent {
id: string;
type: 'video.completed';
createdAt: Date;
data: {
id: string;
status: 'completed';
templateId: string;
url: string;
thumbnailUrl: string;
duration: number;
sizeBytes: number;
creditsConsumed: number;
};
}
interface VideoFailedEvent {
id: string;
type: 'video.failed';
createdAt: Date;
data: {
id: string;
status: 'failed';
templateId: string;
error: {
type: string;
detail: string;
};
};
}
Create Webhook
Copy
const webhook = await bluma.webhooks.create({
url: 'https://myapp.com/webhooks/bluma',
events: ['video.completed', 'video.failed']
});
console.log(webhook.id); // 'webhook_abc123'
console.log(webhook.secret); // 'whsec_...' (save this!)
List Webhooks
Copy
const webhooks = await bluma.webhooks.list();
webhooks.forEach(webhook => {
console.log(webhook.id, webhook.url);
console.log('Active:', webhook.isActive);
});
Delete Webhook
Copy
await bluma.webhooks.delete('webhook_abc123');
Get Webhook Delivery History
Monitor webhook delivery attempts and debug failures:Copy
const deliveries = await bluma.webhooks.getDeliveries('webhook_abc123');
deliveries.deliveries.forEach(delivery => {
console.log(delivery.eventType); // 'video.completed'
console.log(delivery.statusCode); // 200 (or error code)
console.log(delivery.attemptNumber); // Retry attempt number
console.log(delivery.durationMs); // Response time in milliseconds
if (delivery.errorMessage) {
console.log('Failed:', delivery.errorMessage);
}
if (delivery.nextRetryAt) {
console.log('Next retry:', delivery.nextRetryAt);
}
});
Use
getDeliveries() to troubleshoot webhook delivery issues. Check for failed attempts, high latency, or error patterns.Template Variants
Template variants let you save and reuse configuration presets for templates, making it easy to maintain consistent settings across multiple video generations.Create a Variant Preset
Copy
const variant = await bluma.variants.create('rick-morty-explainer', {
name: 'Funny Tone Preset',
settings: {
systemPrompt: 'Use a funny, lighthearted tone',
captionPrompt: 'Create engaging captions with emojis',
compositionProps: {
voiceId: 'female-casual',
primaryColor: '#FF69B4'
}
}
});
console.log(variant.id); // 'variant_abc123'
console.log(variant.name); // 'Funny Tone Preset'
List Variant Presets
Copy
const variants = await bluma.variants.list('rick-morty-explainer');
variants.forEach(variant => {
console.log(variant.name);
console.log(variant.payload.settings);
});
Get Variant Details
Copy
const variant = await bluma.variants.get('rick-morty-explainer', 'variant_abc123');
console.log(variant.name);
console.log(variant.payload);
console.log(variant.isActive);
Update Variant
Copy
const updated = await bluma.variants.update('rick-morty-explainer', 'variant_abc123', {
name: 'Updated Preset Name',
settings: {
systemPrompt: 'New system prompt'
}
});
Delete Variant
Copy
await bluma.variants.delete('rick-morty-explainer', 'variant_abc123');
Asset Collections API
Collections help you organize assets (images, videos, audio) into named groups for easy management and random selection in video generation.Create a Collection
Copy
const collection = await bluma.collections.create({
name: 'Product Photos',
description: 'High-quality product photography',
assetType: 'images' // 'images' | 'videos' | 'all'
});
console.log(collection.id); // 'collection_abc123'
console.log(collection.name); // 'Product Photos'
List Collections
Copy
const collections = await bluma.collections.list();
collections.forEach(collection => {
console.log(collection.name);
console.log(collection.assetType);
});
Get Collection Details
Copy
const collection = await bluma.collections.get('collection_abc123');
console.log(collection.name);
console.log(collection.description);
console.log(collection.assetType);
console.log(collection.createdAt);
Update Collection
Copy
const updated = await bluma.collections.update('collection_abc123', {
name: 'Updated Collection Name',
description: 'New description',
assetType: 'all'
});
Add Assets to Collection
Copy
await bluma.collections.addAsset('collection_abc123', 'asset_xyz789');
Remove Asset from Collection
Copy
await bluma.collections.removeAsset('collection_abc123', 'asset_xyz789');
List Assets in Collection
Copy
const assets = await bluma.collections.listAssets('collection_abc123');
assets.forEach(asset => {
console.log(asset.displayName);
console.log(asset.cloudfrontUrl);
});
Delete Collection
Copy
await bluma.collections.delete('collection_abc123');
Assets API
The Assets API allows you to upload, manage, and organize media files (images, videos, audio) for use in video generation.Upload Assets
Copy
// Browser environment
const fileInput = document.querySelector('input[type="file"]');
const files = Array.from(fileInput.files);
const uploadedAssets = await bluma.assets.upload({
files: files,
collectionId: 'collection_abc123' // Optional
});
uploadedAssets.forEach(asset => {
console.log(asset.id);
console.log(asset.displayName);
console.log(asset.cloudfrontUrl);
});
// Node.js environment
import fs from 'fs';
const fileBuffer = fs.readFileSync('./image.jpg');
const uploadedAssets = await bluma.assets.upload({
files: [fileBuffer],
collectionId: 'collection_abc123'
});
Get Asset Details
Copy
const asset = await bluma.assets.get('asset_xyz789');
console.log(asset.displayName);
console.log(asset.fileType); // 'image/jpeg'
console.log(asset.fileSizeBytes); // 1024000
console.log(asset.width); // 1920
console.log(asset.height); // 1080
console.log(asset.cloudfrontUrl); // CDN URL
List Assets
Copy
// List all assets
const allAssets = await bluma.assets.list();
// Filter by collection
const collectionAssets = await bluma.assets.list({
collectionId: 'collection_abc123'
});
// Filter by source type
const uploadedAssets = await bluma.assets.list({
sourceType: 'uploaded' // 'uploaded' | 'ai_generated'
});
// Filter by file type
const images = await bluma.assets.list({
fileType: 'image/jpeg'
});
allAssets.forEach(asset => {
console.log(asset.displayName);
console.log(asset.sourceType);
console.log(asset.cloudfrontUrl);
});
Get Random Assets
Copy
// Get 3 random assets from a collection
const randomAssets = await bluma.assets.getRandom('collection_abc123', 3);
randomAssets.forEach(asset => {
console.log(asset.cloudfrontUrl);
});
Rename Asset
Copy
const renamed = await bluma.assets.rename('asset_xyz789', 'New Asset Name.jpg');
console.log(renamed.displayName); // 'New Asset Name.jpg'
Delete Asset (Soft Delete)
Copy
await bluma.assets.delete('asset_xyz789');
Recover Deleted Asset
Copy
const recovered = await bluma.assets.recover('asset_xyz789');
console.log(recovered.deletedAt); // null
Error Handling
Error Types
Copy
import {
BlumaError,
ValidationError,
AuthenticationError,
InsufficientCreditsError,
RateLimitError,
NotFoundError,
APIError
} from '@stephen_turtles/bluma-sdk';
try {
const video = await bluma.videos.create({...});
} catch (error) {
if (error instanceof ValidationError) {
console.error('Invalid input:', error.message);
console.error('Field:', error.field);
} else if (error instanceof AuthenticationError) {
console.error('Invalid API key');
} else if (error instanceof InsufficientCreditsError) {
console.error('Out of credits!');
console.error('Required:', error.creditsRequired);
console.error('Available:', error.creditsAvailable);
} else if (error instanceof RateLimitError) {
console.error('Rate limited');
console.error('Retry after:', error.retryAfter, 'seconds');
} else if (error instanceof NotFoundError) {
console.error('Resource not found');
} else if (error instanceof APIError) {
console.error('API error:', error.message);
console.error('Status:', error.status);
console.error('Type:', error.type);
} else {
console.error('Network error:', error);
}
}
Automatic Retries
Copy
// Retries are enabled by default
const bluma = new Bluma({
apiKey: process.env.BLUMA_API_KEY,
maxRetries: 3, // Number of retries (default: 3)
retryDelay: 1000, // Initial delay in ms (default: 1000)
retryMultiplier: 2 // Exponential backoff multiplier (default: 2)
});
// Retry sequence: 1s, 2s, 4s
// Only retries on 5xx errors and network failures
Usage Analytics API
Track your API usage, monitor performance, and identify patterns with comprehensive analytics.Get Usage Metrics
Get aggregated usage statistics for a time period:Copy
const metrics = await bluma.usage.getMetrics({
period: '7d' // '24h' | '7d' | '30d' | '90d'
});
console.log(metrics.totalRequests); // 1000
console.log(metrics.successfulRequests); // 950
console.log(metrics.failedRequests); // 50
console.log(metrics.averageLatency); // 250.5ms
console.log(metrics.creditsConsumed); // 500
console.log(metrics.period.start); // Start date
console.log(metrics.period.end); // End date
Copy
const metrics = await bluma.usage.getMetrics({
startDate: '2024-01-01',
endDate: '2024-01-31'
});
Get Time Series Data
Analyze usage trends over time with granular data points:Copy
const timeseries = await bluma.usage.getTimeSeries({
startDate: '2024-01-01',
endDate: '2024-01-31',
granularity: 'day' // 'hour' | 'day'
});
timeseries.forEach(point => {
console.log(point.timestamp); // Date of data point
console.log(point.requests); // Request count
console.log(point.latency); // Average latency (ms)
console.log(point.successRate); // Success rate (0-1)
});
Use hourly granularity for short time periods (24-48 hours) and daily granularity for longer periods to get meaningful insights without overwhelming data.
Get Top Endpoints
Identify your most-used API endpoints:Copy
const endpoints = await bluma.usage.getTopEndpoints({
limit: 10,
startDate: '2024-01-01',
endDate: '2024-01-31'
});
endpoints.forEach(endpoint => {
console.log(endpoint.endpoint); // '/videos'
console.log(endpoint.method); // 'POST'
console.log(endpoint.requests); // 1000
console.log(endpoint.averageLatency); // 300ms
console.log(endpoint.errorRate); // 0.02 (2%)
});
Get Recent Requests
View recent API requests for debugging:Copy
const requests = await bluma.usage.getRecentRequests({
limit: 20 // Default: 10, Max: 100
});
requests.forEach(req => {
console.log(req.id); // 'req_abc123'
console.log(req.method); // 'POST'
console.log(req.endpoint); // '/videos'
console.log(req.statusCode); // 200
console.log(req.latency); // 250ms
console.log(req.timestamp); // Date object
});
Get Usage by API Key
Track usage across multiple API keys:Copy
const usageByKey = await bluma.usage.getByKey({
startDate: '2024-01-01',
endDate: '2024-01-31'
});
usageByKey.forEach(usage => {
console.log(usage.apiKeyId); // 'key_abc123'
console.log(usage.apiKeyName); // 'Production Key'
console.log(usage.requests); // 500
console.log(usage.creditsConsumed); // 250
});
Get Error Breakdown
Analyze error patterns:Copy
const errors = await bluma.usage.getErrorBreakdown({
startDate: '2024-01-01',
endDate: '2024-01-31'
});
errors.forEach(error => {
console.log(error.statusCode); // 404
console.log(error.count); // 10
console.log(error.percentage); // 0.05 (5% of all errors)
});
API Keys Management
Programmatically manage your API keys for different environments and security workflows.List API Keys
List all API keys for your account:Copy
const apiKeys = await bluma.apiKeys.list();
apiKeys.forEach(key => {
console.log(key.id); // 'key_abc123'
console.log(key.name); // 'Production Key'
console.log(key.environment); // 'production' | 'test'
console.log(key.lastUsed); // Date object (or undefined)
console.log(key.createdAt); // Date object
});
The
key property (containing the actual secret) is only included when creating or rotating a key. It’s never returned by list() or get() for security reasons.Create API Key
Create a new API key for a specific environment:Copy
const apiKey = await bluma.apiKeys.create({
name: 'Staging Environment',
environment: 'test' // 'test' | 'production'
});
console.log(apiKey.id); // 'key_abc123'
console.log(apiKey.key); // 'sk_test_...' ⚠️ SAVE THIS SECURELY!
console.log(apiKey.name); // 'Staging Environment'
The full API key secret is only shown once during creation. Store it securely - you won’t be able to retrieve it again. If you lose it, you’ll need to rotate or create a new key.
Rotate API Key
Generate a new secret for an existing API key:Copy
const rotated = await bluma.apiKeys.rotate('key_abc123');
console.log(rotated.key); // New key value ⚠️ SAVE THIS!
// Old key is immediately invalidated
Rotate keys regularly (e.g., every 90 days) as a security best practice. When rotating, update your application with the new key before the old one expires.
Delete API Key
Permanently delete an API key:Copy
await bluma.apiKeys.delete('key_abc123');
// Key is permanently deleted and cannot be recovered
Deleting an API key immediately invalidates it. Any applications using this key will start receiving authentication errors. Make sure to remove or update the key in your applications first.
Advanced Usage
Batch Operations
Copy
// Generate multiple videos in parallel
const templates = ['consumerclub-discord-zoomed', 'ugc-text-overlay', 'cat-explainer-v2'];
const videos = await Promise.all(
templates.map(templateId =>
bluma.videos.create({
templateId,
context: { prompt: `Create a ${templateId} video` }
})
)
);
console.log('Created', videos.length, 'videos');
// Wait for all to complete
const completed = await Promise.all(
videos.map(video => bluma.videos.waitFor(video.id))
);
TypeScript Types
Import Types
Copy
import type {
// Videos
Video,
VideoStatus,
VideoCreateParams,
// Templates & Variants
Template,
TemplateVariant,
VariantSettings,
VariantCreateParams,
VariantUpdateParams,
// Collections
Collection,
AssetType,
CollectionCreateParams,
CollectionUpdateParams,
// Assets
Asset,
AssetSourceType,
AssetUploadParams,
AssetListParams,
// Credits
CreditBalance,
Transaction,
// Webhooks
Webhook,
WebhookEvent
} from '@stephen_turtles/bluma-sdk';
function processVideo(video: Video) {
if (video.status === VideoStatus.Completed) {
console.log(video.url);
}
}
// Use collection with proper typing
function createImageCollection(collection: Collection) {
if (collection.assetType === 'images') {
console.log('Image-only collection');
}
}
// Use asset with type safety
function processAsset(asset: Asset) {
if (asset.sourceType === 'uploaded') {
console.log('User-uploaded asset');
} else {
console.log('AI-generated asset');
}
}
Type Guards
Copy
import { isVideoCompleted, isVideoFailed } from '@stephen_turtles/bluma-sdk';
const video = await bluma.videos.get('batch_abc123');
if (isVideoCompleted(video)) {
// TypeScript knows video.url is defined
console.log(video.url);
} else if (isVideoFailed(video)) {
// TypeScript knows video.error is defined
console.error(video.error.detail);
}
Examples
Complete Video Generation Flow
Copy
import { Bluma } from '@stephen_turtles/bluma-sdk';
const bluma = new Bluma({
apiKey: process.env.BLUMA_API_KEY
});
async function generateAndDownload() {
try {
// 1. Create video
console.log('Creating video...');
const video = await bluma.videos.create({
templateId: 'consumerclub-discord-zoomed',
context: {
prompt: 'Create a fun personality quiz about food'
}
});
console.log('Video ID:', video.id);
console.log('Credits charged:', video.creditsCharged);
// 2. Wait for completion
console.log('Waiting for video to complete...');
const completed = await bluma.videos.waitFor(video.id, {
onProgress: (progress) => {
console.log(`Progress: ${progress}%`);
}
});
console.log('Video completed!');
// 3. Download video
const download = await bluma.videos.download(completed.id);
// 4. Save to file
const response = await fetch(download.downloadUrl);
const buffer = await response.arrayBuffer();
await fs.writeFile('my-video.mp4', Buffer.from(buffer));
console.log('Video saved to my-video.mp4');
} catch (error) {
if (error instanceof InsufficientCreditsError) {
console.error('Out of credits! Please purchase more.');
} else {
console.error('Error:', error);
}
}
}
generateAndDownload();
Express.js Integration
Copy
import express from 'express';
import { Bluma } from '@stephen_turtles/bluma-sdk';
const app = express();
const bluma = new Bluma({ apiKey: process.env.BLUMA_API_KEY });
app.use(express.json());
app.post('/api/videos', async (req, res) => {
try {
const video = await bluma.videos.create({
templateId: req.body.templateId,
context: req.body.context
});
res.json(video);
} catch (error) {
if (error instanceof BlumaError) {
res.status(error.status).json({ error: error.message });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
app.get('/api/videos/:id', async (req, res) => {
try {
const video = await bluma.videos.get(req.params.id);
res.json(video);
} catch (error) {
if (error instanceof NotFoundError) {
res.status(404).json({ error: 'Video not found' });
} else {
res.status(500).json({ error: 'Internal server error' });
}
}
});
app.listen(3000);
Asset-Driven Video Generation
Copy
import { Bluma } from '@stephen_turtles/bluma-sdk';
import fs from 'fs';
const bluma = new Bluma({
apiKey: process.env.BLUMA_API_KEY
});
async function generateWithAssets() {
try {
// 1. Create a collection for product images
console.log('Creating collection...');
const collection = await bluma.collections.create({
name: 'Product Images',
description: 'High-quality product photos',
assetType: 'images'
});
// 2. Upload assets to the collection
console.log('Uploading assets...');
const image1 = fs.readFileSync('./product-1.jpg');
const image2 = fs.readFileSync('./product-2.jpg');
const uploadedAssets = await bluma.assets.upload({
files: [image1, image2],
collectionId: collection.id
});
console.log(`Uploaded ${uploadedAssets.length} assets`);
// 3. Generate video using the collection
console.log('Generating video...');
const video = await bluma.videos.create({
templateId: 'ugc-text-overlay',
context: {
prompt: 'Create a dynamic product showcase',
productName: 'Amazing Gadget',
collectionId: collection.id // Use assets from collection
}
});
// 4. Wait for completion
const completed = await bluma.videos.waitFor(video.id);
console.log('Video ready:', completed.url);
return {
videoId: completed.id,
collectionId: collection.id,
assetCount: uploadedAssets.length
};
} catch (error) {
console.error('Error:', error);
throw error;
}
}
generateWithAssets();
Next Steps
API Reference
Complete REST API documentation
Python SDK
Python SDK documentation
Best Practices
Production integration patterns
npm Package
View package on npm