Skip to main content

Overview

Follow these best practices to build reliable, efficient, and scalable applications with SlideVid’s API.

Authentication & Security

Store API Keys Securely

Never commit API keys to version control
# .env (add to .gitignore)
SLIDEVID_API_KEY=your_secret_key_here
// ✅ Good - Use environment variables
const apiKey = process.env.SLIDEVID_API_KEY;

// ❌ Bad - Hardcoded
const apiKey = 'sk_live_abc123...';

Use Different Keys for Environments

# .env.development
SLIDEVID_API_KEY=sk_dev_...

# .env.production
SLIDEVID_API_KEY=sk_live_...

Rotate Keys Regularly

  • Rotate API keys every 90 days
  • Immediately rotate if compromised
  • Use key rotation without downtime:
const PRIMARY_KEY = process.env.SLIDEVID_API_KEY_PRIMARY;
const BACKUP_KEY = process.env.SLIDEVID_API_KEY_BACKUP;

async function makeRequestWithFailover(endpoint, options) {
  try {
    return await makeRequest(endpoint, { ...options, apiKey: PRIMARY_KEY });
  } catch (error) {
    if (error.code === 'INVALID_API_KEY') {
      // Failover to backup key
      return await makeRequest(endpoint, { ...options, apiKey: BACKUP_KEY });
    }
    throw error;
  }
}

Error Handling

Implement Comprehensive Error Handling

class SlideVidAPIClient {
  async request(endpoint, options) {
    try {
      const response = await fetch(endpoint, options);
      const data = await response.json();
      
      if (!response.ok) {
        throw new APIError(data.message, response.status, data.code);
      }
      
      return data;
      
    } catch (error) {
      if (error instanceof APIError) {
        // Handle known API errors
        this.handleAPIError(error);
      } else if (error.name === 'TypeError') {
        // Network error
        throw new Error('Network connection failed');
      } else {
        // Unknown error
        this.logError(error);
        throw error;
      }
    }
  }
  
  handleAPIError(error) {
    switch (error.code) {
      case 'RATE_LIMIT_EXCEEDED':
        // Wait and retry
        break;
      case 'INVALID_API_KEY':
        // Alert admin
        break;
      case 'VALIDATION_ERROR':
        // Fix request data
        break;
      default:
        throw error;
    }
  }
}

Use Retry Logic with Exponential Backoff

async function retryWithBackoff(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn();
    } catch (error) {
      // Don't retry on client errors (4xx except 429)
      if (error.status >= 400 && error.status < 500 && error.status !== 429) {
        throw error;
      }
      
      if (i === maxRetries - 1) throw error;
      
      // Exponential backoff: 2^i * 1000ms
      const delay = Math.pow(2, i) * 1000;
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

// Usage
const result = await retryWithBackoff(() => 
  api.createVideo(videoData)
);

Performance Optimization

Cache Resources

class ResourceCache {
  constructor(ttl = 3600000) { // 1 hour
    this.cache = new Map();
    this.ttl = ttl;
  }
  
  async get(key, fetchFn) {
    const cached = this.cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }
    
    const data = await fetchFn();
    this.cache.set(key, { data, timestamp: Date.now() });
    return data;
  }
}

const cache = new ResourceCache();

// Cache avatars list
const avatars = await cache.get('avatars', () => 
  api.getAvatars()
);

// Cache voices list
const voices = await cache.get('voices', () => 
  api.getVoices()
);

Batch Operations

// ❌ Bad - Sequential requests
for (const recipient of recipients) {
  await api.createVideo(recipient);
}

// ✅ Good - Batched with concurrency limit
async function batchProcess(items, batchSize = 5) {
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    await Promise.all(batch.map(item => api.createVideo(item)));
  }
}

await batchProcess(recipients, 5);

Minimize API Calls

// ❌ Bad - Multiple calls for same data
const avatar1 = await api.getAvatar('avatar_123');
const avatar2 = await api.getAvatar('avatar_123'); // Duplicate!

// ✅ Good - Fetch once, reuse
const avatars = await api.getAvatars();
const avatar = avatars.find(a => a.id === 'avatar_123');

Webhook Implementation

Always Use Webhooks, Not Polling

// ❌ Bad - Polling
async function waitForVideo(projectId) {
  while (true) {
    const video = await api.getVideo(projectId);
    if (video.status === 'completed') return video;
    await new Promise(r => setTimeout(r, 10000)); // Wasteful!
  }
}

// ✅ Good - Webhook
app.post('/webhook', (req, res) => {
  const { projectId, status, video } = req.body;
  if (status === 'completed') {
    handleCompletedVideo(projectId, video);
  }
  res.status(200).send('OK');
});

Implement Idempotency

const processedWebhooks = new Set();

app.post('/webhook', (req, res) => {
  const { projectId } = req.body;
  
  // Prevent duplicate processing
  if (processedWebhooks.has(projectId)) {
    return res.status(200).send('Already processed');
  }
  
  processedWebhooks.add(projectId);
  
  // Process webhook
  processWebhook(req.body);
  
  res.status(200).send('OK');
});

Return 200 Immediately

// ✅ Good - Respond fast, process async
app.post('/webhook', async (req, res) => {
  // Respond immediately
  res.status(200).send('OK');
  
  // Process asynchronously
  setImmediate(async () => {
    try {
      await processWebhook(req.body);
    } catch (error) {
      console.error('Webhook processing error:', error);
    }
  });
});

Rate Limiting

Monitor Rate Limits

class RateLimitTracker {
  checkHeaders(response) {
    const limit = response.headers.get('X-RateLimit-Limit');
    const remaining = response.headers.get('X-RateLimit-Remaining');
    const reset = response.headers.get('X-RateLimit-Reset');
    
    console.log(`Rate limit: ${remaining}/${limit}, resets at ${new Date(reset * 1000)}`);
    
    if (remaining < 10) {
      console.warn('⚠️ Approaching rate limit!');
    }
  }
}

Implement Request Queuing

import PQueue from 'p-queue';

const queue = new PQueue({
  concurrency: 5,
  interval: 1000,
  intervalCap: 10
});

async function queuedRequest(endpoint, options) {
  return queue.add(() => fetch(endpoint, options));
}

Logging & Monitoring

Structured Logging

class APILogger {
  log(level, message, meta = {}) {
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      level,
      message,
      ...meta
    }));
  }
  
  info(message, meta) { this.log('info', message, meta); }
  error(message, meta) { this.log('error', message, meta); }
  warn(message, meta) { this.log('warn', message, meta); }
}

const logger = new APILogger();

// Usage
logger.info('Video created', {
  projectId: 'proj_123',
  duration: 1250,
  status: 'success'
});

Track Key Metrics

class MetricsCollector {
  constructor() {
    this.metrics = {
      requests: 0,
      successes: 0,
      failures: 0,
      totalDuration: 0
    };
  }
  
  async trackRequest(fn) {
    this.metrics.requests++;
    const start = Date.now();
    
    try {
      const result = await fn();
      this.metrics.successes++;
      return result;
    } catch (error) {
      this.metrics.failures++;
      throw error;
    } finally {
      this.metrics.totalDuration += Date.now() - start;
    }
  }
  
  getStats() {
    return {
      ...this.metrics,
      successRate: (this.metrics.successes / this.metrics.requests) * 100,
      avgDuration: this.metrics.totalDuration / this.metrics.requests
    };
  }
}

const metrics = new MetricsCollector();

// Track API calls
await metrics.trackRequest(() => api.createVideo(data));

// Log stats
console.log(metrics.getStats());

Data Validation

Validate Before Sending

const videoSchema = {
  type: { required: true, enum: ['class', 'ugc_ads'] },
  script: { required: true, minLength: 10, maxLength: 5000 },
  avatarId: { required: true, pattern: /^avatar_/ },
  voiceId: { required: true, pattern: /^voice_/ }
};

function validateVideoData(data, schema) {
  const errors = [];
  
  Object.entries(schema).forEach(([field, rules]) => {
    const value = data[field];
    
    if (rules.required && !value) {
      errors.push(`${field} is required`);
    }
    
    if (rules.enum && !rules.enum.includes(value)) {
      errors.push(`${field} must be one of: ${rules.enum.join(', ')}`);
    }
    
    if (rules.minLength && value.length < rules.minLength) {
      errors.push(`${field} must be at least ${rules.minLength} characters`);
    }
    
    if (rules.pattern && !rules.pattern.test(value)) {
      errors.push(`${field} has invalid format`);
    }
  });
  
  return { valid: errors.length === 0, errors };
}

// Validate before API call
const validation = validateVideoData(videoData, videoSchema);
if (!validation.valid) {
  throw new Error(`Validation failed: ${validation.errors.join(', ')}`);
}

Testing

Unit Test API Client

// api-client.test.js
import { describe, it, expect, vi } from 'vitest';
import { SlideVidAPIClient } from './api-client';

describe('SlideVidAPIClient', () => {
  it('should create video successfully', async () => {
    const client = new SlideVidAPIClient('test_key');
    
    global.fetch = vi.fn(() =>
      Promise.resolve({
        ok: true,
        json: () => Promise.resolve({
          success: true,
          data: { projectId: 'proj_123' }
        })
      })
    );
    
    const result = await client.createVideo({
      type: 'class',
      script: 'Test script'
    });
    
    expect(result.projectId).toBe('proj_123');
  });
  
  it('should handle errors', async () => {
    const client = new SlideVidAPIClient('test_key');
    
    global.fetch = vi.fn(() =>
      Promise.resolve({
        ok: false,
        status: 400,
        json: () => Promise.resolve({
          success: false,
          message: 'Invalid request'
        })
      })
    );
    
    await expect(client.createVideo({})).rejects.toThrow('Invalid request');
  });
});

Integration Testing

// integration.test.js
describe('Video Creation Flow', () => {
  it('should create and track video', async () => {
    const api = new SlideVidAPIClient(process.env.TEST_API_KEY);
    
    // 1. Get resources
    const avatars = await api.getAvatars();
    expect(avatars.length).toBeGreaterThan(0);
    
    // 2. Create video
    const projectId = await api.createVideo({
      type: 'class',
      script: 'Integration test video',
      avatarId: avatars[0].id
    });
    expect(projectId).toBeTruthy();
    
    // 3. Verify video exists
    const videos = await api.listVideos();
    expect(videos.some(v => v.id === projectId)).toBe(true);
  });
});

Code Organization

Create Reusable SDK

// slidevid-sdk.js
export class SlideVidSDK {
  constructor(apiKey, options = {}) {
    this.apiKey = apiKey;
    this.baseUrl = options.baseUrl || 'https://api.slidevid.ai/v1';
    this.cache = new ResourceCache();
    this.logger = new APILogger();
  }
  
  // Resource methods
  async getAvatars(type = 'all') { /* ... */ }
  async getVoices(filters = {}) { /* ... */ }
  async getMusic() { /* ... */ }
  
  // Template methods
  async listTemplates() { /* ... */ }
  async getTemplate(id) { /* ... */ }
  
  // Video methods
  async createVideo(data) { /* ... */ }
  async listVideos(filters) { /* ... */ }
  
  // High-level helpers
  async createFromScratch(script, options) { /* ... */ }
  async generateFromTemplate(templateId, variables) { /* ... */ }
  async bulkGenerate(templateId, recipients) { /* ... */ }
}

// Usage
import { SlideVidSDK } from './slidevid-sdk';

const slidevid = new SlideVidSDK(process.env.SLIDEVID_API_KEY);
await slidevid.createVideo({ /* ... */ });

Deployment

Environment Configuration

// config.js
const config = {
  development: {
    apiKey: process.env.SLIDEVID_DEV_KEY,
    baseUrl: 'https://dev.tryslidevid.ai/api/v1',
    webhookUrl: 'https://dev-tunnel.ngrok.io/webhook',
    logLevel: 'debug'
  },
  staging: {
    apiKey: process.env.SLIDEVID_STAGING_KEY,
    baseUrl: 'https://staging.tryslidevid.ai/api/v1',
    webhookUrl: 'https://staging.myapp.com/webhook',
    logLevel: 'info'
  },
  production: {
    apiKey: process.env.SLIDEVID_API_KEY,
    baseUrl: 'https://api.slidevid.ai/v1',
    webhookUrl: 'https://myapp.com/webhook',
    logLevel: 'error'
  }
};

export default config[process.env.NODE_ENV || 'development'];

Health Checks

// health-check.js
async function checkAPIHealth() {
  try {
    const response = await fetch('https://api.slidevid.ai/v1/avatar/list', {
      headers: { 'x-api-key': process.env.SLIDEVID_API_KEY }
    });
    
    if (!response.ok) {
      throw new Error(`API health check failed: ${response.status}`);
    }
    
    return { status: 'healthy', timestamp: new Date() };
  } catch (error) {
    return { status: 'unhealthy', error: error.message, timestamp: new Date() };
  }
}

// Run health check every 5 minutes
setInterval(async () => {
  const health = await checkAPIHealth();
  console.log('API Health:', health);
  
  if (health.status === 'unhealthy') {
    // Alert ops team
    alertOps('SlideVid API is unhealthy', health);
  }
}, 5 * 60 * 1000);

Production Checklist

Before going to production:
Security
  • API keys stored in environment variables
  • Different keys for dev/staging/production
  • Webhook endpoints use HTTPS
  • Webhook signature verification implemented
  • Rate limiting tracked and handled
Reliability
  • Comprehensive error handling
  • Retry logic with exponential backoff
  • Webhook idempotency implemented
  • Webhook returns 200 immediately
  • Failed requests logged and alerted
Performance
  • Resources cached appropriately
  • Batch operations used for bulk tasks
  • Unnecessary API calls eliminated
  • Request timeouts configured
Monitoring
  • Structured logging implemented
  • Key metrics tracked
  • Health checks configured
  • Error alerts set up
  • Dashboard for monitoring
Testing
  • Unit tests for API client
  • Integration tests pass
  • Webhook endpoint tested
  • Error scenarios tested
  • Load testing completed

Quick Reference