Installation
npm install @stephen_turtles/bluma-sdk
- 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
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');
});
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');
}
}
);
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
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
API Reference
Complete REST API documentation
Python SDK
Python SDK documentation
Best Practices
Production integration patterns
npm Package
View package on npm