Installation

npm install @stephen_turtles/bluma-sdk
Requirements:
  • Node.js 18+ or Bun 1.0+
  • TypeScript 5.0+ (for TypeScript projects)

Quick Start

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

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

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

// 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
Type Definition:
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

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

// 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

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

const templates = await bluma.templates.list();

templates.forEach(template => {
  console.log(template.id, template.name);
  console.log('Base cost:', template.baseCost, 'credits');
});
Response Type:
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

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

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

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

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');
    }
  }
);
Event Types:
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

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

const webhooks = await bluma.webhooks.list();

webhooks.forEach(webhook => {
  console.log(webhook.id, webhook.url);
  console.log('Active:', webhook.isActive);
});

Delete Webhook

await bluma.webhooks.delete('webhook_abc123');

Get Webhook Delivery History

Monitor webhook delivery attempts and debug failures:
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

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

const variants = await bluma.variants.list('rick-morty-explainer');

variants.forEach(variant => {
  console.log(variant.name);
  console.log(variant.payload.settings);
});

Get Variant Details

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

const updated = await bluma.variants.update('rick-morty-explainer', 'variant_abc123', {
  name: 'Updated Preset Name',
  settings: {
    systemPrompt: 'New system prompt'
  }
});

Delete Variant

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

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

const collections = await bluma.collections.list();

collections.forEach(collection => {
  console.log(collection.name);
  console.log(collection.assetType);
});

Get Collection Details

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

const updated = await bluma.collections.update('collection_abc123', {
  name: 'Updated Collection Name',
  description: 'New description',
  assetType: 'all'
});

Add Assets to Collection

await bluma.collections.addAsset('collection_abc123', 'asset_xyz789');

Remove Asset from Collection

await bluma.collections.removeAsset('collection_abc123', 'asset_xyz789');

List Assets in Collection

const assets = await bluma.collections.listAssets('collection_abc123');

assets.forEach(asset => {
  console.log(asset.displayName);
  console.log(asset.cloudfrontUrl);
});

Delete Collection

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

// 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

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

// 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

// 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

const renamed = await bluma.assets.rename('asset_xyz789', 'New Asset Name.jpg');

console.log(renamed.displayName); // 'New Asset Name.jpg'

Delete Asset (Soft Delete)

await bluma.assets.delete('asset_xyz789');

Recover Deleted Asset

const recovered = await bluma.assets.recover('asset_xyz789');

console.log(recovered.deletedAt); // null

Error Handling

Error Types

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

// 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:
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
You can also specify custom date ranges:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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

// 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

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

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

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

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

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

Support