# Stack0 > AI-native infrastructure platform for modern applications. Production-ready email, CDN, integrations, screenshots, AI extraction, crawl, map, and document parsing. Open Firecrawl alternative. ## What is Stack0? Stack0 provides essential infrastructure services for developers building web applications: - **Email Service**: Send transactional and marketing emails with templates, tracking, and analytics - **CDN**: Asset storage and delivery with on-the-fly image transformations - **Integrations**: Unified API to connect your users' third-party apps (CRM, storage, communication, productivity) - **Webdata**: Screenshots, AI extraction, recursive **crawl**, URL **map**, PDF/DOCX **documents** parsing, **actions** (click/type/scroll), **stealth** mode - **Workflows**: Multi-step AI pipelines with sequential/parallel execution and variable passing - **Domain Management**: Custom domain verification and DNS management All services are usage-based with no monthly minimums. Get started with $5 in free credits. ## Quick Start ### 1. Create an Account Sign up at https://app.stack0.dev and create your first organization. ### 2. Get Your API Key Navigate to Settings → API Keys in your dashboard and generate a new API key. ### 3. Install the SDK ```bash npm install @stack0/sdk # or bun add @stack0/sdk ``` ### 4. Install Elements (Optional) Pre-built React components for file uploads, avatars, and image galleries: ```bash # Install the package npm install @stack0/elements # Or with shadcn CLI (recommended) npx shadcn@latest add https://www.stack0.dev/r/file-upload npx shadcn@latest add https://www.stack0.dev/r/avatar-upload npx shadcn@latest add https://www.stack0.dev/r/logo-upload npx shadcn@latest add https://www.stack0.dev/r/image-gallery npx shadcn@latest add https://www.stack0.dev/r/image ``` ### 5. Send Your First Email ```typescript import { Stack0 } from '@stack0/sdk'; const stack0 = new Stack0({ apiKey: process.env.STACK0_API_KEY, }); await stack0.mail.send({ from: 'hello@yourdomain.com', to: 'user@example.com', subject: 'Welcome to our app!', html: '

Thanks for signing up!

', }); ``` ### 6. Upload Your First Asset ```typescript const asset = await stack0.cdn.upload({ projectSlug: 'my-project', file: imageBuffer, filename: 'hero.jpg', mimeType: 'image/jpeg', }); console.log(asset.cdnUrl); // https://cdn.stack0.dev/... ``` ### 7. Capture Your First Screenshot ```typescript const screenshot = await stack0.screenshots.captureAndWait({ url: 'https://example.com', format: 'png', fullPage: true, blockAds: true, }); console.log(screenshot.imageUrl); // https://cdn.stack0.dev/screenshots/... ``` ### 8. Extract Data with AI ```typescript const extraction = await stack0.extraction.extractAndWait({ url: 'https://example.com/product', mode: 'schema', schema: { type: 'object', properties: { title: { type: 'string' }, price: { type: 'number' }, }, }, }); console.log(extraction.extractedData); // { title: '...', price: 29.99 } ``` ### 9. Connect Your Users' Apps (Integrations) ```typescript // List contacts from any connected CRM (HubSpot, Salesforce, Pipedrive) const contacts = await stack0.integrations.crm.listContacts({ connectionId: 'conn_abc123', // User's connection ID }); // Upload a file to user's Google Drive, Dropbox, or OneDrive await stack0.integrations.storage.uploadFile({ connectionId: 'conn_def456', name: 'report.pdf', content: fileBuffer, folderId: 'folder_123', }); // Send a message to user's Slack channel await stack0.integrations.communication.sendMessage({ connectionId: 'conn_ghi789', channelId: 'C1234567890', text: 'Hello from your app!', }); ``` ### 10. Run an AI Workflow ```typescript // Run a multi-step AI pipeline const result = await stack0.workflows.runAndWait({ projectSlug: 'my-project', workflowId: 'content-pipeline', variables: { topic: 'AI in Healthcare', }, }); console.log(result.output); // Generated content from all steps ``` ## Elements - React Components `@stack0/elements` provides pre-built React components for common file handling patterns when working with Stack0 CDN. ### Installation ```bash # Using npm npm install @stack0/elements # Using pnpm pnpm add @stack0/elements # Using bun bun add @stack0/elements # Using shadcn CLI (recommended - copies components to your project) npx shadcn@latest add https://www.stack0.dev/r/file-upload npx shadcn@latest add https://www.stack0.dev/r/avatar-upload npx shadcn@latest add https://www.stack0.dev/r/logo-upload npx shadcn@latest add https://www.stack0.dev/r/image-gallery npx shadcn@latest add https://www.stack0.dev/r/image ``` Required peer dependencies: ```bash npm install class-variance-authority clsx tailwind-merge lucide-react ``` ### Available Components - **FileUpload**: Drag-and-drop file upload with progress tracking and multi-file support - **AvatarUpload**: Circular avatar upload with preview, fallback initials, and remove functionality - **LogoUpload**: Rectangular logo upload with multiple aspect ratios and SVG support - **ImageGallery**: Responsive image grid with lightbox, selection, and action support - **Image**: next/image wrapper optimized for Stack0 CDN with built-in transforms ### Upload Handler Pattern Elements use a handler pattern that works with any backend. Your server handles authentication and returns presigned URLs: ```typescript // Client-side usage import { FileUpload, createStack0Handler } from '@stack0/elements' const handler = createStack0Handler({ endpoint: '/api/upload', // Your API endpoint }) ``` ```typescript // Server-side API route (Next.js App Router) // app/api/upload/route.ts import { stack0 } from '@stack0/sdk' export async function POST(request: Request) { const { filename, mimeType, size, folder } = await request.json() const result = await stack0.cdn.createUpload({ filename, mimeType, size, folder, public: true, }) return Response.json({ uploadUrl: result.uploadUrl, assetId: result.assetId, }) } ``` ### Custom Upload Handler Create your own handler for any backend: ```typescript import { FileUpload, type UploadHandler } from '@stack0/elements' const customHandler: UploadHandler = { getUploadUrl: async (file) => { const response = await fetch('/api/upload', { method: 'POST', body: JSON.stringify({ filename: file.name, mimeType: file.type, size: file.size, }), }) const data = await response.json() return { uploadUrl: data.uploadUrl, assetId: data.assetId, } }, onUploadComplete: async (assetId, file) => { await fetch(`/api/upload/${assetId}/confirm`, { method: 'POST' }) return { url: `https://cdn.example.com/${assetId}` } }, onError: (error, file) => { console.error(`Upload failed for ${file.name}:`, error) }, } ``` ### Component Examples #### FileUpload ```tsx import { FileUpload, createStack0Handler } from '@stack0/elements' const handler = createStack0Handler({ endpoint: '/api/upload' }) console.log(files)} /> ``` #### AvatarUpload ```tsx import { AvatarUpload, createStack0Handler } from '@stack0/elements' const handler = createStack0Handler({ endpoint: '/api/upload', folder: '/avatars' }) updateUser({ avatarUrl: url })} fallback={user.initials} size="lg" /> ``` #### Image (CDN-optimized) ```tsx import { Image } from '@stack0/elements' Photo ``` ## CDN API Reference The CDN API enables you to upload, manage, and transform assets. All CDN operations are project-scoped using `projectSlug`. ### Upload File Upload files directly with the high-level `upload` method: ```typescript const asset = await stack0.cdn.upload({ projectSlug: 'my-project', file: fileBuffer, // Blob, Buffer, or ArrayBuffer filename: 'photo.jpg', mimeType: 'image/jpeg', folder: '/images/avatars', // Optional folder path metadata: { userId: 'user_123' }, // Optional custom metadata }); // Response { id: 'asset_abc123', filename: 'photo.jpg', originalFilename: 'photo.jpg', mimeType: 'image/jpeg', size: 102400, type: 'image', cdnUrl: 'https://cdn.stack0.dev/...', width: 1920, height: 1080, status: 'ready', folder: '/images/avatars', createdAt: Date } ``` ### Upload with Watermark Automatically apply a watermark to images during upload: ```typescript const asset = await stack0.cdn.upload({ projectSlug: 'my-project', file: photoBuffer, filename: 'branded-photo.jpg', mimeType: 'image/jpeg', watermark: { assetId: 'logo-asset-id', // Reference another CDN asset // Or use url: 'https://example.com/logo.png' for external images position: 'bottom-right', // 9 position options opacity: 50, // 0-100 sizingMode: 'relative', // 'relative' or 'absolute' width: 15, // 15% of image width (relative mode) offsetX: 20, // 20px from edge offsetY: 20, }, }); ``` #### Watermark Options - `assetId`: Reference to another CDN asset (your logo, etc.) - `url`: Direct URL to watermark image (alternative to assetId) - `position`: Placement on image - `top-left`, `top-center`, `top-right` - `center-left`, `center`, `center-right` - `bottom-left`, `bottom-center`, `bottom-right` - `offsetX`, `offsetY`: Pixel offset from position (default: 0) - `sizingMode`: How to interpret width/height - `absolute`: Width/height in exact pixels - `relative`: Width/height as percentage of main image (1-100) - `width`, `height`: Size of watermark (based on sizingMode) - `opacity`: Transparency 0-100 (default: 100 = fully opaque) - `rotation`: Rotation angle -360 to 360 degrees (default: 0) - `tile`: Repeat watermark across image (default: false) - `tileSpacing`: Spacing between tiles in pixels (default: 100) - `borderRadius`: Corner radius in pixels (default: 0) #### Manual Upload Flow For more control, use the presigned URL flow: ```typescript // 1. Get presigned upload URL const { uploadUrl, assetId, expiresAt } = await stack0.cdn.getUploadUrl({ projectSlug: 'my-project', filename: 'document.pdf', mimeType: 'application/pdf', size: 1048576, // File size in bytes folder: '/documents', }); // 2. Upload directly to S3 await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': 'application/pdf' }, }); // 3. Confirm upload completed const asset = await stack0.cdn.confirmUpload(assetId); ``` ### Upload from URL (Server-Side) Upload a file by providing a source URL. The server fetches and stores the file directly, so the client never downloads the bytes. Ideal for serverless environments with tight memory limits. ```typescript const asset = await stack0.cdn.uploadFromUrl({ projectSlug: 'my-project', sourceUrl: 'https://replicate.delivery/output/video.mp4', filename: 'video.mp4', mimeType: 'video/mp4', folder: '/ai-generated', metadata: { source: 'replicate' }, }); console.log(asset.cdnUrl); // https://cdn.stack0.dev/... ``` ### List Assets Query assets with filters and pagination: ```typescript const { assets, total, hasMore } = await stack0.cdn.list({ projectSlug: 'my-project', type: 'image', // 'image' | 'video' | 'audio' | 'document' | 'other' status: 'ready', // 'pending' | 'processing' | 'ready' | 'failed' folder: '/images', search: 'avatar', // Search in filename tags: ['profile', 'user'], sortBy: 'createdAt', // 'createdAt' | 'filename' | 'size' | 'type' sortOrder: 'desc', // 'asc' | 'desc' limit: 20, offset: 0, }); ``` ### Get Asset Retrieve a single asset by ID: ```typescript const asset = await stack0.cdn.get('asset_abc123'); console.log(asset.filename); console.log(asset.cdnUrl); console.log(asset.width, asset.height); // For images/videos console.log(asset.duration); // For videos/audio (seconds) ``` ### Update Asset Update asset metadata: ```typescript const asset = await stack0.cdn.update({ id: 'asset_abc123', filename: 'renamed-photo.jpg', folder: '/images/archived', tags: ['nature', 'sunset'], alt: 'A beautiful sunset over the ocean', metadata: { category: 'landscape' }, }); ``` ### Delete Assets ```typescript // Delete single asset await stack0.cdn.delete('asset_abc123'); // Delete multiple assets const { deletedCount } = await stack0.cdn.deleteMany([ 'asset_abc123', 'asset_def456', 'asset_ghi789', ]); ``` ### Move Assets Move assets to a different folder: ```typescript await stack0.cdn.move({ assetIds: ['asset_abc123', 'asset_def456'], folder: '/images/archive', // Use null for root folder }); ``` ### Image Transformations Generate optimized and transformed image URLs client-side (no API call required): ```typescript // Using asset's cdnUrl directly (recommended) const url = stack0.cdn.getTransformUrl(asset.cdnUrl, { width: 800, height: 600, fit: 'cover', // 'cover' | 'contain' | 'fill' | 'inside' | 'outside' format: 'webp', // 'webp' | 'jpeg' | 'png' | 'avif' | 'auto' quality: 80, // 1-100 }); // Or using s3Key when cdnUrl is configured in Stack0 options const stack0 = new Stack0({ apiKey: process.env.STACK0_API_KEY, cdnUrl: 'https://cdn.yourproject.stack0.dev', }); const url = stack0.cdn.getTransformUrl(asset.s3Key, { width: 400 }); // Use transformed URL // Optimized image ``` #### Transformation Options - `width`: Target width in pixels (snapped to optimal sizes for caching) - `height`: Target height in pixels - `fit`: How to fit the image - `cover`: Crop to fill dimensions (default) - `contain`: Fit within dimensions, preserving aspect ratio - `fill`: Stretch to fill dimensions - `inside`: Fit inside dimensions - `outside`: Cover dimensions while preserving aspect ratio - `format`: Output format (webp recommended for web, 'auto' for browser detection) - `quality`: Compression quality (1-100) - `crop`: Smart crop position ('attention', 'entropy', 'center', etc.) - `blur`: Blur sigma (0.3-100) - `sharpen`: Sharpen sigma - `brightness`: Brightness adjustment (-100 to 100) - `saturation`: Saturation adjustment (-100 to 100) - `grayscale`: Convert to grayscale (boolean) - `rotate`: Rotation angle (0, 90, 180, 270) - `flip`: Flip vertically (boolean) - `flop`: Flip horizontally (boolean) ### Folders Organize assets with virtual folders: ```typescript // Get folder tree const tree = await stack0.cdn.getFolderTree({ projectSlug: 'my-project', maxDepth: 3, }); // Tree structure [ { id: 'folder_123', name: 'images', path: '/images', assetCount: 42, children: [ { id: 'folder_456', name: 'avatars', path: '/images/avatars', assetCount: 15, children: [] } ] } ] // Create folder const folder = await stack0.cdn.createFolder({ projectSlug: 'my-project', name: 'avatars', parentId: 'folder_123', // Optional, null for root }); // Delete folder await stack0.cdn.deleteFolder('folder_123'); await stack0.cdn.deleteFolder('folder_123', true); // Delete with contents ``` ### Asset Types Assets are automatically categorized by MIME type: - `image`: jpeg, png, gif, webp, svg, etc. - `video`: mp4, webm, mov, avi, etc. - `audio`: mp3, wav, ogg, etc. - `document`: pdf, doc, docx, txt, etc. - `other`: All other file types ## Video Streaming API Reference The Video Streaming API enables you to transcode videos into HLS adaptive streaming and MP4 formats for optimal playback across devices. ### Transcode Video Start a transcoding job to convert video to HLS or MP4: ```typescript const job = await stack0.cdn.transcode({ projectSlug: 'my-project', assetId: 'video-asset-id', outputFormat: 'hls', // 'hls' for adaptive streaming, 'mp4' for progressive download variants: [ { quality: '720p', codec: 'h264' }, { quality: '1080p', codec: 'h264' }, ], webhookUrl: 'https://your-app.com/webhook', // Optional: get notified when complete }); // Response { id: 'job_abc123', assetId: 'asset_xyz', status: 'pending', outputFormat: 'hls', variants: [{ quality: '720p', codec: 'h264' }, { quality: '1080p', codec: 'h264' }], progress: 0, createdAt: Date } ``` #### Transcode Options - `assetId` (required): ID of the video asset to transcode - `outputFormat` (required): `hls` for adaptive streaming or `mp4` for progressive download - `variants` (required): Array of quality variants (1-10) - `quality`: `360p`, `480p`, `720p`, `1080p`, `1440p`, `2160p` - `codec`: `h264` (default) or `h265` - `bitrate`: Custom bitrate in kbps (optional) - `maxFramerate`: Maximum frame rate (optional) - `watermark`: Add watermark to video (optional) - `type`: `image` or `text` - `imageAssetId`: Asset ID for image watermark - `text`: Text for text watermark - `position`: `top-left`, `top-right`, `bottom-left`, `bottom-right`, `center` - `opacity`: 0-100 (default 50) - `trim`: Trim video (optional) - `start`: Start time in seconds - `end`: End time in seconds - `webhookUrl`: URL to receive completion notification ### Get Transcoding Job Status ```typescript const job = await stack0.cdn.getJob('job_abc123'); console.log(`Status: ${job.status}`); console.log(`Progress: ${job.progress}%`); // Status values: 'pending' | 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled' ``` ### List Transcoding Jobs ```typescript const { jobs, total, hasMore } = await stack0.cdn.listJobs({ projectSlug: 'my-project', assetId: 'video-asset-id', // Optional: filter by asset status: 'processing', // Optional: filter by status limit: 20, offset: 0, }); ``` ### Cancel Transcoding Job ```typescript await stack0.cdn.cancelJob('job_abc123'); ``` ### Get Streaming URLs After transcoding completes, get URLs for playback: ```typescript const urls = await stack0.cdn.getStreamingUrls('asset-id'); // HLS adaptive streaming (recommended for web/mobile) console.log(`HLS Master Playlist: ${urls.hlsUrl}`); // MP4 direct download URLs by quality for (const mp4 of urls.mp4Urls) { console.log(`${mp4.quality}: ${mp4.url}`); } // Generated thumbnails for (const thumb of urls.thumbnails) { console.log(`Thumbnail at ${thumb.timestamp}s: ${thumb.url}`); } ``` ### Generate Thumbnails Extract thumbnail images from video at specific timestamps: ```typescript const thumbnail = await stack0.cdn.getThumbnail({ assetId: 'video-asset-id', timestamp: 10.5, // 10.5 seconds into the video width: 320, // Optional: resize width format: 'webp', // 'jpg', 'png', 'webp' }); console.log(`Thumbnail URL: ${thumbnail.url}`); ``` ### Extract Audio Extract audio track from video as MP3, AAC, or WAV: ```typescript const { jobId, status } = await stack0.cdn.extractAudio({ projectSlug: 'my-project', assetId: 'video-asset-id', format: 'mp3', // 'mp3', 'aac', 'wav' bitrate: 192, // Optional: kbps }); ``` ### Generate GIF Create an animated GIF from a video segment: ```typescript const gif = await stack0.cdn.generateGif({ projectSlug: 'my-project', assetId: 'video-asset-id', startTime: 5, // Start at 5 seconds into the video duration: 3, // 3 second GIF (max 30 seconds) width: 480, // Output width in pixels (100-800) fps: 10, // Frames per second (5-30) optimizePalette: true, // Two-pass palette optimization for smaller file size }); // Response { id: 'gif_abc123', assetId: 'asset_xyz', startTime: 5, duration: 3, fps: 10, url: null, // null until completed status: 'pending', createdAt: Date } ``` #### GIF Generation Options - `assetId` (required): ID of the video asset - `startTime`: Start position in seconds (default: 0) - `duration`: Duration in seconds, 0.5-30 (default: 5) - `width`: Output width 100-800px (default: 480, height auto-calculated) - `fps`: Frames per second 5-30 (default: 10, lower = smaller file) - `optimizePalette`: Use two-pass palette generation for better quality and smaller file size (default: true) #### Get GIF Status ```typescript const gif = await stack0.cdn.getGif('gif_abc123'); if (gif?.status === 'completed') { console.log(`GIF URL: ${gif.url}`); console.log(`Size: ${gif.sizeBytes} bytes`); console.log(`Frames: ${gif.frameCount}`); } // Status values: 'pending' | 'processing' | 'completed' | 'failed' ``` #### List GIFs for a Video ```typescript const gifs = await stack0.cdn.listGifs({ assetId: 'video-asset-id' }); for (const gif of gifs) { console.log(`${gif.startTime}s-${gif.startTime + gif.duration}s: ${gif.url}`); } ``` #### GIF Best Practices - **Duration**: Keep GIFs under 10 seconds for reasonable file sizes - **Width**: 480px is a good balance of quality and file size - **FPS**: 10 fps is sufficient for most use cases; use 15-20 for smoother motion - **File Size**: A 5-second 480px GIF at 10fps is typically 1-5MB depending on content ### Video Player Integration Use HLS.js for cross-browser HLS playback: ```typescript import Hls from 'hls.js'; const urls = await stack0.cdn.getStreamingUrls('asset-id'); const video = document.getElementById('video') as HTMLVideoElement; if (Hls.isSupported() && urls.hlsUrl) { const hls = new Hls(); hls.loadSource(urls.hlsUrl); hls.attachMedia(video); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari native HLS support video.src = urls.hlsUrl!; } ``` ### Video Transcoding Status Flow 1. **pending**: Job created, waiting to be queued 2. **queued**: Job queued for processing 3. **processing**: Video being transcoded 4. **completed**: Transcoding finished, streaming URLs available 5. **failed**: Transcoding failed (check `errorMessage`) 6. **cancelled**: Job was cancelled ## Video Merge API Reference The Video Merge API enables you to combine multiple videos and images into a single video with optional audio overlay. Perfect for creating slideshows, video compilations, or adding background music. ### Create Merge Job Combine multiple assets (videos, images) into a single video: ```typescript const job = await stack0.cdn.createMergeJob({ projectSlug: 'my-project', inputs: [ { assetId: 'intro-video-id' }, // Full video { assetId: 'image-id', duration: 5 }, // Image for 5 seconds { assetId: 'main-video-id', startTime: 10, endTime: 60 }, // Trimmed video ], audioTrack: { assetId: 'background-music-id', loop: true, // Loop if audio is shorter than video fadeIn: 2, // 2 second fade in fadeOut: 3, // 3 second fade out }, output: { format: 'mp4', // 'mp4' or 'webm' quality: '1080p', // '360p', '480p', '720p', '1080p', '1440p', '2160p' filename: 'final-video.mp4', }, webhookUrl: 'https://your-app.com/webhook', }); // Response { id: 'merge_abc123', status: 'pending', progress: 0, outputFormat: 'mp4', outputQuality: '1080p', createdAt: Date } ``` #### Merge Input Options Each input item in the `inputs` array supports: - `assetId` (required): UUID of the video or image asset - `duration`: Display duration in seconds (required for images, max 3600) - `startTime`: Trim start point in seconds (videos only) - `endTime`: Trim end point in seconds (videos only) - `textOverlay`: Text caption configuration (see below) #### Text Overlay Options Add text captions/overlays to images and videos, perfect for TikTok/Instagram style content: - `text` (required): The text to display (1-500 characters) - `position`: Vertical position - `top`, `center`, `bottom` (default: `bottom`) - `fontSize`: Font size in pixels (12-200, default: 48) - `fontFamily`: Font family (default: `Liberation Sans`) - `fontWeight`: `normal` or `bold` (default: `bold`) - `color`: Text color in hex format (default: `#FFFFFF`) - `backgroundColor`: Background color (e.g., `rgba(0,0,0,0.5)`) - `padding`: Padding from edges in pixels (0-100, default: 20) - `maxWidth`: Maximum width as percentage of video width (10-100, default: 90) - `shadow`: Shadow configuration - `color`: Shadow color in hex (default: `#000000`) - `offsetX`: Horizontal offset (0-20, default: 2) - `offsetY`: Vertical offset (0-20, default: 2) - `stroke`: Stroke/outline configuration - `color`: Stroke color in hex (default: `#000000`) - `width`: Stroke width in pixels (1-10, default: 2) #### Audio Track Options - `assetId` (required): UUID of the audio file - `loop`: Loop audio if shorter than video (default: false) - `fadeIn`: Fade in duration (0-10 seconds) - `fadeOut`: Fade out duration (0-10 seconds) #### Output Options - `format`: Output format - `mp4` (default) or `webm` - `quality`: Quality preset - `360p`, `480p`, `720p` (default), `1080p`, `1440p`, `2160p` - `filename`: Custom filename for the output (max 255 chars) ### Get Merge Job Status ```typescript const job = await stack0.cdn.getMergeJob('merge_abc123'); console.log(`Status: ${job.status}`); console.log(`Progress: ${job.progress}%`); if (job.status === 'completed' && job.outputAsset) { console.log(`Output video: ${job.outputAsset.cdnUrl}`); console.log(`Duration: ${job.outputAsset.duration}s`); console.log(`Size: ${job.outputAsset.size} bytes`); } // Status values: 'pending' | 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled' ``` ### List Merge Jobs ```typescript const { jobs, total, hasMore } = await stack0.cdn.listMergeJobs({ projectSlug: 'my-project', status: 'completed', // Optional: filter by status limit: 20, offset: 0, }); ``` ### Cancel Merge Job ```typescript await stack0.cdn.cancelMergeJob('merge_abc123'); ``` ### Merge Job Status Flow 1. **pending**: Job created, waiting to be queued 2. **queued**: Job queued for processing 3. **processing**: Video being merged 4. **completed**: Merge finished, output asset available 5. **failed**: Merge failed (check `errorMessage`) 6. **cancelled**: Job was cancelled ### Use Cases #### Photo Slideshow with Music ```typescript const job = await stack0.cdn.createMergeJob({ projectSlug: 'my-project', inputs: [ { assetId: 'photo-1', duration: 3 }, { assetId: 'photo-2', duration: 3 }, { assetId: 'photo-3', duration: 3 }, ], audioTrack: { assetId: 'background-music', fadeIn: 1, fadeOut: 2, }, output: { format: 'mp4', quality: '1080p', }, }); ``` #### TikTok/Instagram Style Video with Text Captions ```typescript const job = await stack0.cdn.createMergeJob({ projectSlug: 'my-project', inputs: [ { assetId: 'intro-image', duration: 3, textOverlay: { text: 'Welcome to my story!', position: 'bottom', fontSize: 64, fontWeight: 'bold', color: '#FFFFFF', stroke: { color: '#000000', width: 2 }, shadow: { color: '#000000', offsetX: 2, offsetY: 2 }, }, }, { assetId: 'main-content-video', startTime: 0, endTime: 15, textOverlay: { text: 'This is the main part', position: 'bottom', fontSize: 48, color: '#FFFFFF', backgroundColor: 'rgba(0,0,0,0.5)', padding: 20, }, }, { assetId: 'outro-image', duration: 3, textOverlay: { text: 'Follow for more!', position: 'center', fontSize: 72, fontWeight: 'bold', color: '#FFD700', stroke: { color: '#000000', width: 3 }, }, }, ], audioTrack: { assetId: 'trending-sound', loop: true, fadeOut: 2, }, output: { format: 'mp4', quality: '1080p', }, }); ``` #### Video Compilation with Trimming ```typescript const job = await stack0.cdn.createMergeJob({ projectSlug: 'my-project', inputs: [ { assetId: 'intro-video' }, { assetId: 'main-footage', startTime: 30, endTime: 90 }, // 60 second clip { assetId: 'title-card-image', duration: 5 }, { assetId: 'outro-video', endTime: 10 }, // First 10 seconds only ], output: { format: 'mp4', quality: '1080p', filename: 'highlight-reel.mp4', }, }); ``` ### Merge Limits - Maximum inputs per job: 100 - Maximum image duration: 3600 seconds (1 hour) - Maximum fade duration: 10 seconds ### Asset Status Flow 1. **pending**: Upload URL generated, waiting for upload 2. **processing**: File uploaded, being processed (thumbnails, metadata extraction) 3. **ready**: Asset ready for use 4. **failed**: Processing failed ### CDN URL Structure Assets are served from the CDN with the following URL patterns: - Original: `https://cdn.stack0.dev/{s3Key}` - Transformed: `https://cdn.stack0.dev/{s3Key}?w=800&h=600&fit=cover&f=webp&q=80` ### Private Files Private files are stored in a separate secure bucket with no public CDN access. They can only be accessed through presigned download URLs with configurable expiration times (1 hour to 7 days). #### Upload Private File ```typescript // High-level upload (handles presigned URL flow) const file = await stack0.cdn.uploadPrivate({ projectSlug: 'my-project', file: fileBuffer, // Blob, Buffer, or ArrayBuffer filename: 'confidential.pdf', mimeType: 'application/pdf', folder: '/contracts', description: 'Q4 Sales Contract', metadata: { clientId: 'client_123' }, }); // Response { id: 'file_abc123', filename: 'confidential.pdf', mimeType: 'application/pdf', size: 102400, status: 'ready', folder: '/contracts', createdAt: Date } ``` #### Manual Upload Flow ```typescript // 1. Get presigned upload URL const { uploadUrl, fileId, expiresAt } = await stack0.cdn.getPrivateUploadUrl({ projectSlug: 'my-project', filename: 'report.xlsx', mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', size: 204800, }); // 2. Upload directly to S3 await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, }); // 3. Confirm upload completed const privateFile = await stack0.cdn.confirmPrivateUpload(fileId); ``` #### Generate Download URL Create a temporary presigned download URL: ```typescript const { downloadUrl, expiresAt } = await stack0.cdn.getPrivateDownloadUrl({ fileId: 'file_abc123', expiresIn: 86400, // 24 hours (1hr to 7 days supported) }); // Use the URL for secure downloads // URL is valid only until expiresAt ``` #### List Private Files ```typescript const { files, total, hasMore } = await stack0.cdn.listPrivateFiles({ projectSlug: 'my-project', folder: '/contracts', status: 'ready', search: 'contract', sortBy: 'createdAt', sortOrder: 'desc', limit: 20, offset: 0, }); ``` #### Update Private File ```typescript const file = await stack0.cdn.updatePrivateFile({ fileId: 'file_abc123', description: 'Updated description', folder: '/contracts/archived', tags: ['contract', '2024'], }); ``` #### Delete Private Files ```typescript // Single file await stack0.cdn.deletePrivateFile('file_abc123'); // Multiple files const { deletedCount } = await stack0.cdn.deletePrivateFiles([ 'file_abc123', 'file_def456', ]); ``` ### Download Bundles Download bundles allow you to create zip archives containing multiple public assets and/or private files. Bundles are created asynchronously via background jobs. #### Create Bundle ```typescript const { bundle } = await stack0.cdn.createBundle({ projectSlug: 'my-project', name: 'Project Assets - Dec 2024', description: 'All project media files', assetIds: ['asset_1', 'asset_2'], // Public CDN assets privateFileIds: ['file_1', 'file_2'], // Private files expiresIn: 86400, // Bundle expires in 24 hours }); // Response { id: 'bundle_abc123', name: 'Project Assets - Dec 2024', status: 'pending', // 'pending' | 'processing' | 'ready' | 'failed' | 'expired' createdAt: Date } ``` #### Check Bundle Status ```typescript const bundle = await stack0.cdn.getBundle('bundle_abc123'); if (bundle.status === 'ready') { console.log(`Bundle ready: ${bundle.fileCount} files, ${bundle.size} bytes`); } ``` #### Download Bundle ```typescript const { downloadUrl, expiresAt } = await stack0.cdn.getBundleDownloadUrl({ bundleId: 'bundle_abc123', expiresIn: 3600, // 1 hour }); // Download the zip file window.location.href = downloadUrl; // or const response = await fetch(downloadUrl); ``` #### List Bundles ```typescript const { bundles, total, hasMore } = await stack0.cdn.listBundles({ projectSlug: 'my-project', status: 'ready', search: 'Dec 2024', limit: 20, }); ``` #### Delete Bundle ```typescript await stack0.cdn.deleteBundle('bundle_abc123'); ``` ### Bundle Status Flow 1. **pending**: Bundle created, job queued 2. **processing**: Background worker creating zip archive 3. **ready**: Bundle ready for download 4. **failed**: Bundle creation failed (check error) 5. **expired**: Bundle download link expired ## Mail API Reference The Mail SDK provides a complete email platform including transactional emails, templates, audiences, contacts, campaigns, automated sequences, and event tracking. ### Core Email Operations #### Send Email ```typescript const result = await stack0.mail.send({ from: 'noreply@yourdomain.com', to: 'user@example.com', subject: 'Hello World', html: '

Email content here

', text: 'Email content here', // Optional plaintext }); // With display name const result = await stack0.mail.send({ from: { email: 'noreply@yourdomain.com', name: 'My App' }, to: { email: 'user@example.com', name: 'User' }, subject: 'Hello World', html: '

Email content here

', }); // With threading (for replies) const result = await stack0.mail.send({ from: 'support@yourdomain.com', to: 'user@example.com', subject: 'Re: Your question', html: '

Here is my reply...

', inReplyTo: '', references: '', }); ``` #### Send Batch (up to 100 emails) ```typescript await stack0.mail.sendBatch({ emails: [ { from: 'noreply@example.com', to: 'user1@example.com', subject: 'Hello', html: '

Hi User 1

' }, { from: 'noreply@example.com', to: 'user2@example.com', subject: 'Hello', html: '

Hi User 2

' }, ] }); ``` #### Send Broadcast (same content, up to 1000 recipients) ```typescript await stack0.mail.sendBroadcast({ from: 'newsletter@yourdomain.com', to: ['user1@example.com', 'user2@example.com'], subject: 'Newsletter', html: '

Content

', }); ``` #### Get, List, Resend, Cancel ```typescript const email = await stack0.mail.get('email_id'); const { emails } = await stack0.mail.list({ status: 'delivered', limit: 50 }); await stack0.mail.resend('email_id'); await stack0.mail.cancel('email_id'); // For scheduled emails ``` #### Analytics ```typescript const analytics = await stack0.mail.getAnalytics(); const timeSeries = await stack0.mail.getTimeSeriesAnalytics({ days: 30 }); const hourly = await stack0.mail.getHourlyAnalytics(); const { senders } = await stack0.mail.listSenders(); ``` ### Domains (stack0.mail.domains) Manage sending domains with DNS verification. ```typescript // List domains const domains = await stack0.mail.domains.list({ projectSlug: 'my-project' }); // Add and verify domain const { dnsRecords } = await stack0.mail.domains.add({ domain: 'yourdomain.com' }); const records = await stack0.mail.domains.getDnsRecords('domain_id'); const { verified } = await stack0.mail.domains.verify('domain_id'); // Set default and delete await stack0.mail.domains.setDefault('domain_id'); await stack0.mail.domains.delete('domain_id'); ``` ### Templates (stack0.mail.templates) Create and manage reusable email templates with variable substitution. ```typescript // CRUD operations const template = await stack0.mail.templates.create({ name: 'Welcome Email', slug: 'welcome', subject: 'Welcome {{name}}!', html: '

Hi {{name}}

Thanks for joining!

', }); const { templates } = await stack0.mail.templates.list({ search: 'welcome' }); const template = await stack0.mail.templates.get('template_id'); const template = await stack0.mail.templates.getBySlug('welcome'); await stack0.mail.templates.update({ id: 'template_id', subject: 'New subject' }); await stack0.mail.templates.delete('template_id'); // Preview with variables const preview = await stack0.mail.templates.preview({ id: 'template_id', variables: { name: 'John' }, }); ``` ### Audiences (stack0.mail.audiences) Organize contacts into lists for targeted email campaigns. ```typescript // CRUD const audience = await stack0.mail.audiences.create({ name: 'Newsletter Subscribers' }); const { audiences } = await stack0.mail.audiences.list({ search: 'newsletter' }); const audience = await stack0.mail.audiences.get('audience_id'); await stack0.mail.audiences.update({ id: 'audience_id', name: 'New Name' }); await stack0.mail.audiences.delete('audience_id'); // Manage contacts in audience const { contacts } = await stack0.mail.audiences.listContacts({ id: 'audience_id' }); await stack0.mail.audiences.addContacts({ id: 'audience_id', contactIds: ['c1', 'c2'] }); await stack0.mail.audiences.removeContacts({ id: 'audience_id', contactIds: ['c1'] }); ``` ### Contacts (stack0.mail.contacts) Manage individual contacts with metadata. ```typescript // CRUD const contact = await stack0.mail.contacts.create({ email: 'user@example.com', firstName: 'John', lastName: 'Doe', metadata: { company: 'Acme' }, }); const { contacts } = await stack0.mail.contacts.list({ search: 'john', status: 'subscribed' }); const contact = await stack0.mail.contacts.get('contact_id'); await stack0.mail.contacts.update({ id: 'contact_id', firstName: 'Jane' }); await stack0.mail.contacts.delete('contact_id'); // Bulk import const result = await stack0.mail.contacts.import({ audienceId: 'audience_id', contacts: [ { email: 'a@example.com', firstName: 'Alice' }, { email: 'b@example.com', firstName: 'Bob' }, ], }); ``` ### Campaigns (stack0.mail.campaigns) Create and send email campaigns to audiences. ```typescript // CRUD const campaign = await stack0.mail.campaigns.create({ name: 'Product Launch', subject: 'Introducing Our New Product', fromEmail: 'news@yourdomain.com', audienceId: 'audience_id', templateId: 'template_id', }); const { campaigns } = await stack0.mail.campaigns.list({ status: 'draft' }); const campaign = await stack0.mail.campaigns.get('campaign_id'); await stack0.mail.campaigns.update({ id: 'campaign_id', subject: 'New Subject' }); await stack0.mail.campaigns.delete('campaign_id'); // Lifecycle await stack0.mail.campaigns.send({ id: 'campaign_id', sendNow: true }); await stack0.mail.campaigns.send({ id: 'campaign_id', scheduledAt: new Date('2025-01-15') }); await stack0.mail.campaigns.pause('campaign_id'); await stack0.mail.campaigns.cancel('campaign_id'); const duplicate = await stack0.mail.campaigns.duplicate('campaign_id'); // Statistics const stats = await stack0.mail.campaigns.getStats('campaign_id'); ``` ### Sequences (stack0.mail.sequences) Create automated email sequences. Supported triggers: 'contact_created', 'contact_updated' (set triggerConfig.propertyName, optional propertyValue), 'contact_added_to_list' (set triggerConfig.audienceId), and 'event_received' (set triggerConfig.eventName). Contacts can also be enrolled manually with addContact(). ```typescript // CRUD const sequence = await stack0.mail.sequences.create({ name: 'Onboarding Flow', triggerType: 'event_received', triggerConfig: { eventName: 'user_signup' }, }); const { sequences } = await stack0.mail.sequences.list({ status: 'active' }); const sequence = await stack0.mail.sequences.get('sequence_id'); // Includes nodes, connections, and avgCompletionMs (avg time to complete, ms) // Lifecycle await stack0.mail.sequences.publish('sequence_id'); await stack0.mail.sequences.pause('sequence_id'); await stack0.mail.sequences.resume('sequence_id'); await stack0.mail.sequences.archive('sequence_id'); const duplicate = await stack0.mail.sequences.duplicate('sequence_id'); // Node management const node = await stack0.mail.sequences.createNode({ id: 'sequence_id', nodeType: 'email', name: 'Welcome Email', positionX: 100, positionY: 200, }); await stack0.mail.sequences.setNodeEmail('sequence_id', { nodeId: 'node_id', subject: 'Welcome!', templateId: 'template_id', }); await stack0.mail.sequences.setNodeTimer('sequence_id', { nodeId: 'node_id', delayAmount: 3, delayUnit: 'days', }); // Connections between nodes await stack0.mail.sequences.createConnection({ id: 'sequence_id', sourceNodeId: 'node_1', targetNodeId: 'node_2', }); // Contact entries (paginated + searchable by email/name) const { entries } = await stack0.mail.sequences.listEntries({ id: 'sequence_id', search: 'jane@', status: 'active', limit: 25, offset: 0 }); await stack0.mail.sequences.addContact({ id: 'sequence_id', contactId: 'contact_id' }); await stack0.mail.sequences.removeContact({ id: 'sequence_id', entryId: 'entry_id' }); // Analytics const analytics = await stack0.mail.sequences.getAnalytics('sequence_id'); ``` ### Events (stack0.mail.events) Define and track custom events to trigger sequences and segment contacts. ```typescript // Define events const event = await stack0.mail.events.create({ name: 'purchase_completed', description: 'Triggered when a user completes a purchase', propertiesSchema: { properties: [ { name: 'amount', type: 'number', required: true }, { name: 'product_id', type: 'string' }, ], }, }); const { events } = await stack0.mail.events.list(); const event = await stack0.mail.events.get('event_id'); // Track events (triggers sequences) await stack0.mail.events.track({ eventName: 'purchase_completed', contactEmail: 'user@example.com', properties: { amount: 99.99, product_id: 'prod_123' }, }); // Batch tracking await stack0.mail.events.trackBatch({ events: [ { eventName: 'page_viewed', contactEmail: 'a@example.com', properties: { page: '/pricing' } }, { eventName: 'page_viewed', contactEmail: 'b@example.com', properties: { page: '/features' } }, ], }); // View occurrences and analytics const { occurrences } = await stack0.mail.events.listOccurrences({ eventId: 'event_id' }); const analytics = await stack0.mail.events.getAnalytics('event_id'); ``` ### Agent Mailboxes (stack0.mail.mailboxes) Create email accounts for AI agents that can send AND receive. Inbound emails are delivered via webhook. #### Create a Mailbox ```typescript const mailbox = await stack0.mail.mailboxes.create({ address: 'remi', // local part (auto-generated if omitted) displayName: 'Remi', domain: 'agents.mastagents.com', // must be a verified domain webhookUrl: 'https://mastagents.com/api/webhooks/inbound-email', metadata: { agentId: 'abc123' }, maxInboundPerDay: 1000, // rate limit (default: 1000) }); // mailbox.address = "remi@agents.mastagents.com" ``` #### Send as the Agent Use the standard `mail.send()` with the mailbox address: ```typescript await stack0.mail.send({ from: { email: 'remi@agents.mastagents.com', name: 'Remi' }, to: 'craig@company.com', subject: 'Daily briefing', html: '

Here is your daily briefing...

', }); ``` #### Manage Mailboxes ```typescript const { mailboxes } = await stack0.mail.mailboxes.list({ domain: 'agents.mastagents.com' }); const mailbox = await stack0.mail.mailboxes.get('mailbox_id'); await stack0.mail.mailboxes.update({ id: 'mailbox_id', status: 'paused' }); await stack0.mail.mailboxes.delete('mailbox_id'); ``` #### Inbound Messages When someone replies to your agent's email, Stack0 delivers the message to your webhook URL: ```json { "event": "email.inbound", "mailbox": "remi@agents.mastagents.com", "mailboxId": "...", "from": { "email": "craig@company.com", "name": "Craig" }, "to": "remi@agents.mastagents.com", "subject": "Re: Daily briefing", "text": "Thanks, looks good!", "html": "

Thanks, looks good!

", "messageId": "", "inReplyTo": "", "references": [""], "attachments": [], "metadata": { "agentId": "abc123" }, "receivedAt": "2026-04-09T10:30:00Z" } ``` Webhooks are signed with `X-Stack0-Signature` (HMAC-SHA256). Failed deliveries retry with exponential backoff (1m, 5m, 30m, 2h, 12h, max 5 attempts). #### View Inbound Messages ```typescript const { messages } = await stack0.mail.mailboxes.listMessages({ mailboxId: 'mailbox_id' }); const message = await stack0.mail.mailboxes.getMessage('message_id'); ``` #### Setup Requirements To receive inbound email on a verified domain, add an MX record: - **Type**: MX - **Value**: `10 inbound-smtp.us-east-1.amazonaws.com` ### Email Status Flow 1. **pending**: Email created, queued for sending 2. **sent**: Email handed off to mail server 3. **delivered**: Email successfully delivered to recipient 4. **bounced**: Email rejected by recipient server 5. **failed**: Permanent delivery failure ## Screenshots API Reference The Screenshots API enables you to capture high-quality screenshots of any webpage with full browser rendering. ### Basic Screenshot ```typescript const { id, status } = await stack0.screenshots.capture({ url: 'https://example.com', format: 'png', // 'png' | 'jpeg' | 'webp' | 'pdf' fullPage: false, // Capture full scrollable page deviceType: 'desktop', // 'desktop' | 'tablet' | 'mobile' }); // Poll for completion const screenshot = await stack0.screenshots.get({ id }); ``` ### Capture and Wait For synchronous operations, use `captureAndWait`: ```typescript const screenshot = await stack0.screenshots.captureAndWait({ url: 'https://example.com', format: 'png', fullPage: true, blockAds: true, blockCookieBanners: true, }); // Response { id: 'scr_abc123', status: 'completed', imageUrl: 'https://cdn.stack0.dev/screenshots/...', imageWidth: 1280, imageHeight: 720, processingTimeMs: 2340, } ``` #### Screenshot Options - `url` (required): URL to capture - `format`: Output format - `png`, `jpeg`, `webp`, `pdf` - `quality`: Image quality 1-100 (for jpeg/webp) - `fullPage`: Capture full scrollable page - `deviceType`: `desktop` (1280x720), `tablet` (768x1024), `mobile` (375x667) - `viewportWidth`: Custom viewport width (320-3840) - `viewportHeight`: Custom viewport height (240-2160) - `deviceScaleFactor`: Device pixel ratio (1-3) - `waitForSelector`: CSS selector to wait for before capture - `waitForTimeout`: Additional wait time in ms (0-30000) - `blockAds`: Block advertisements - `blockCookieBanners`: Block cookie consent popups - `blockChatWidgets`: Block chat widgets - `blockTrackers`: Block tracking scripts - `darkMode`: Enable dark mode - `customCss`: Inject custom CSS (max 10KB) - `customJs`: Inject custom JavaScript (max 10KB) - `selector`: Capture specific element only - `hideSelectors`: Array of CSS selectors to hide (max 50) - `clickSelector`: Click element before capture - `clip`: Capture specific region `{ x, y, width, height }` ## Integrations API Reference The Integrations API provides a unified interface to access your users' third-party applications. Connect once, access CRM, storage, communication, and productivity tools through one consistent API. ### Supported Categories - **CRM**: HubSpot, Salesforce, Pipedrive, Attio - contacts, companies, deals - **Storage**: Google Drive, Dropbox, OneDrive - files, folders, uploads - **Communication**: Slack, Discord - channels, messages, users - **Productivity**: Notion, Google Sheets, Airtable - documents, tables, rows - **Calendar**: Google Calendar - calendars, events - **Email**: Gmail - messages, threads, labels ### Setting Up Connections Users connect their apps through the IntegrationsConnect component or via OAuth flow: ```typescript // Server-side: Create a connection session for a user const session = await stack0.integrations.createConnectionSession({ endUserId: 'user_123', // Your user's ID connectorSlug: 'hubspot', // Which app to connect redirectUrl: 'https://yourapp.com/integrations/callback', }); // Redirect user to session.authUrl to complete OAuth ``` ### List Available Connectors ```typescript const connectors = await stack0.integrations.listConnectors(); // Returns available integrations [ { slug: 'hubspot', name: 'HubSpot', category: 'crm', status: 'active' }, { slug: 'google-drive', name: 'Google Drive', category: 'storage', status: 'active' }, { slug: 'slack', name: 'Slack', category: 'communication', status: 'active' }, // ... ] ``` ### List User Connections ```typescript const connections = await stack0.integrations.listConnections({ endUserId: 'user_123', }); // Returns user's connected apps [ { id: 'conn_abc123', connectorSlug: 'hubspot', status: 'active', createdAt: Date, }, ] ``` ### CRM Operations ```typescript // List contacts const { contacts, nextCursor } = await stack0.integrations.crm.listContacts({ connectionId: 'conn_abc123', limit: 50, cursor: undefined, // For pagination }); // Create a contact const contact = await stack0.integrations.crm.createContact({ connectionId: 'conn_abc123', data: { email: 'john@example.com', firstName: 'John', lastName: 'Doe', phone: '+1234567890', }, }); // List companies const { companies } = await stack0.integrations.crm.listCompanies({ connectionId: 'conn_abc123', }); // List deals/opportunities const { deals } = await stack0.integrations.crm.listDeals({ connectionId: 'conn_abc123', }); // Create a note await stack0.integrations.crm.createNote({ connectionId: 'conn_abc123', data: { content: 'Follow up on proposal', contactId: 'contact_123', }, }); ``` ### Storage Operations ```typescript // List files in a folder const { files } = await stack0.integrations.storage.listFiles({ connectionId: 'conn_def456', folderId: 'root', // or specific folder ID }); // Upload a file const file = await stack0.integrations.storage.uploadFile({ connectionId: 'conn_def456', name: 'report.pdf', content: fileBuffer, // Buffer or base64 mimeType: 'application/pdf', folderId: 'folder_123', // Optional }); // Download a file const { content, mimeType } = await stack0.integrations.storage.downloadFile({ connectionId: 'conn_def456', fileId: 'file_abc', }); // Create a folder const folder = await stack0.integrations.storage.createFolder({ connectionId: 'conn_def456', name: 'Reports', parentId: 'root', }); ``` ### Communication Operations ```typescript // List channels const { channels } = await stack0.integrations.communication.listChannels({ connectionId: 'conn_ghi789', }); // Send a message await stack0.integrations.communication.sendMessage({ connectionId: 'conn_ghi789', channelId: 'C1234567890', text: 'Hello from your app!', }); // List users const { users } = await stack0.integrations.communication.listUsers({ connectionId: 'conn_ghi789', }); ``` ### Productivity Operations ```typescript // List documents (Notion pages, etc.) const { documents } = await stack0.integrations.productivity.listDocuments({ connectionId: 'conn_jkl012', }); // List tables (Google Sheets, Airtable bases) const { tables } = await stack0.integrations.productivity.listTables({ connectionId: 'conn_jkl012', }); // Get table rows const { rows } = await stack0.integrations.productivity.getTableRows({ connectionId: 'conn_jkl012', tableId: 'table_abc', }); // Create a table row await stack0.integrations.productivity.createTableRow({ connectionId: 'conn_jkl012', tableId: 'table_abc', data: { Name: 'New Item', Status: 'Active', Date: '2025-01-15', }, }); ``` ### Calendar Operations ```typescript // List calendars const { data: calendars } = await stack0.integrations.calendar.listCalendars('conn_abc123'); // List events with time range const { data: events } = await stack0.integrations.calendar.listEvents('conn_abc123', 'primary', { timeMin: new Date('2026-04-01'), timeMax: new Date('2026-04-30'), }); // Create event const event = await stack0.integrations.calendar.createEvent('conn_abc123', 'primary', { title: 'Team Standup', startTime: new Date('2026-04-10T09:00:00Z'), endTime: new Date('2026-04-10T09:30:00Z'), attendees: [{ email: 'teammate@company.com' }], location: 'Zoom', }); // Update / delete event await stack0.integrations.calendar.updateEvent('conn_abc123', 'primary', 'event_id', { title: 'Updated Title' }); await stack0.integrations.calendar.deleteEvent('conn_abc123', 'primary', 'event_id'); ``` ### Email Operations (Gmail) ```typescript // List messages const { data: messages } = await stack0.integrations.mail.listMessages('conn_abc123', { query: 'is:unread' }); // Get message const message = await stack0.integrations.mail.getMessage('conn_abc123', 'msg_id'); // Send email const sent = await stack0.integrations.mail.sendMessage('conn_abc123', { to: ['user@example.com'], subject: 'Hello', body: 'Email body text', }); // Threads const { data: threads } = await stack0.integrations.mail.listThreads('conn_abc123'); const thread = await stack0.integrations.mail.getThread('conn_abc123', 'thread_id'); // Labels const labels = await stack0.integrations.mail.listLabels('conn_abc123'); ``` ### Passthrough API For provider-specific features not covered by the unified API: ```typescript // Make a raw API call to the connected provider const response = await stack0.integrations.passthrough({ connectionId: 'conn_abc123', method: 'GET', path: '/crm/v3/objects/custom_object', query: { limit: '10' }, }); ``` ### Delete Connection ```typescript await stack0.integrations.deleteConnection({ connectionId: 'conn_abc123', }); ``` ### IntegrationsConnect Element Pre-built React component for connecting user apps: ```tsx // Note: Requires a backend proxy for security (API keys must stay server-side) // 1. Create API proxy route (app/api/integrations/[...path]/route.ts) export async function POST(request, { params }) { const session = await auth(); if (!session?.user) return unauthorized(); const path = params.path.join('/'); const body = await request.json(); const response = await fetch(`https://api.stack0.dev/v1/integrations/${path}`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.STACK0_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ ...body, endUserId: session.user.id }), }); return Response.json(await response.json()); } // 2. Use the component import { IntegrationsConnect } from '@stack0/elements'; { console.log('Connected:', connection.connectorSlug); }} onError={(error) => { console.error('Connection failed:', error); }} /> ``` ## AI Extraction API Reference Extract structured data from any webpage using AI. ### Basic Extraction ```typescript const { id, status } = await stack0.extraction.extract({ url: 'https://example.com/article', mode: 'markdown', // 'auto' | 'markdown' | 'schema' | 'raw' }); // Poll for completion const extraction = await stack0.extraction.get({ id }); ``` ### Extract and Wait ```typescript const extraction = await stack0.extraction.extractAndWait({ url: 'https://example.com/product', mode: 'schema', schema: { type: 'object', properties: { title: { type: 'string' }, price: { type: 'number' }, description: { type: 'string' }, inStock: { type: 'boolean' }, }, }, }); // Response { id: 'ext_abc123', status: 'completed', extractedData: { title: 'Product Name', price: 29.99, description: 'Product description...', inStock: true, }, processingTimeMs: 3200, } ``` #### Extraction Modes - `auto`: AI automatically extracts the most relevant content - `markdown`: Convert page content to clean, formatted markdown - `schema`: Extract data matching your custom JSON schema - `html`: Get the raw HTML content #### Extraction Options - `url` (required): URL to extract from - `mode`: Extraction mode - `schema`: JSON Schema for structured extraction (when mode is `schema`) - `prompt`: Custom prompt to guide AI extraction - `includeLinks`: Include links in extracted content - `includeImages`: Include image URLs in extracted content - `includeMetadata`: Include page metadata (title, description, etc.) - `waitForSelector`: CSS selector to wait for before extraction - `waitForTimeout`: Additional wait time in ms ### Get Screenshot/Extraction Status Check the status of an async operation: ```typescript // Get screenshot status const screenshot = await stack0.screenshots.get({ id: 'scr_abc123' }); // Get extraction status const extraction = await stack0.extraction.get({ id: 'ext_abc123' }); ``` ### Status Flow 1. **pending**: Request created, queued for processing 2. **processing**: Being processed by workers 3. **completed**: Successfully completed 4. **failed**: Processing failed (check `error` field) ### Batch Processing Process multiple URLs in parallel: ```typescript // Batch screenshots const batch = await stack0.screenshots.batchAndWait({ urls: [ 'https://example.com/page1', 'https://example.com/page2', 'https://example.com/page3', ], config: { format: 'png', fullPage: true, }, }); // Batch extractions const extractionBatch = await stack0.extraction.batchAndWait({ urls: ['https://example.com/article1', 'https://example.com/article2'], config: { mode: 'markdown', includeMetadata: true, }, }); ``` ### Webhooks Configure webhooks to receive notifications when jobs complete: ```typescript const { id } = await stack0.screenshots.capture({ url: 'https://example.com', webhookUrl: 'https://yourapp.com/webhook', webhookSecret: 'your-secret-key', }); ``` Webhook payload: ```json { "event": "screenshot.completed", "data": { "id": "scr_abc123", "status": "completed", "imageUrl": "https://cdn.stack0.dev/screenshots/...", "processingTimeMs": 2340 } } ``` ### Caching Enable caching to avoid re-processing identical requests: ```typescript const screenshot = await stack0.screenshots.captureAndWait({ url: 'https://example.com', cacheKey: 'homepage-v1', cacheTtl: 3600, // Cache for 1 hour }); ``` ## Crawl API Recursively crawl a website and scrape every page. Honors robots.txt and sitemap.xml. Open Firecrawl alternative. ```typescript const crawl = await stack0.crawl.startAndWait({ url: "https://docs.example.com", maxDepth: 3, maxPages: 200, formats: ["markdown", "links"], respectRobotsTxt: true, stealth: true, }); for (const page of crawl.pages ?? []) { console.log(page.url, page.markdown?.slice(0, 120)); } ``` Endpoints (prefix all with `/v1`): `POST /webdata/crawl`, `GET /webdata/crawl/{id}?includePages=true`, `GET /webdata/crawl/{crawlId}/pages`, `POST /webdata/crawl/{id}/cancel`, `DELETE /webdata/crawl/{id}`, `GET /webdata/crawl` (list). ## Map API Discover every URL on a site in seconds. Uses `sitemap.xml` with a link-crawl fallback. ```typescript const { urls } = await stack0.map.createAndWait({ url: "https://docs.example.com", search: "api", limit: 5000, }); ``` Endpoints (prefix all with `/v1`): `POST /webdata/map`, `GET /webdata/map/{id}`, `GET /webdata/map`, `DELETE /webdata/map/{id}`. ## Documents API Parse PDF and DOCX files into clean markdown. ```typescript const doc = await stack0.documents.parseAndWait({ url: "https://example.com/whitepaper.pdf", }); console.log(doc.pageCount, doc.markdown); ``` Endpoints (prefix all with `/v1`): `POST /webdata/documents`, `GET /webdata/documents/{id}`, `GET /webdata/documents`, `DELETE /webdata/documents/{id}`. ## Actions (multi-step scrapes) Pass an `actions` array on any scrape, extraction, crawl, or search request to drive the page before content is captured. Supported types: `click`, `type`, `press`, `scroll`, `wait`, `screenshot`, `execute_js`, `extract`. ```typescript await stack0.extraction.extractAndWait({ url: "https://example.com/search", mode: "markdown", actions: [ { type: "type", selector: "input[name=q]", text: "playwright" }, { type: "press", key: "Enter" }, { type: "wait", selector: ".results", timeout: 5000 }, { type: "scroll", direction: "down", pixels: 1200 }, { type: "screenshot" }, ], }); ``` ## Stealth & Proxy Every webdata request accepts `stealth: true` (default) which applies playwright-extra's evasion patches. Set `proxy: "residential" | "datacenter"` and optional `proxyLocation: "us"` to route through your configured proxy provider (configure via `WEBDATA_PROXY_SERVER` env var). ## MCP Server The Stack0 MCP server exposes crawl, map, search, and document tools alongside screenshots and extraction: - `crawl_start`, `crawl_get`, `crawl_list`, `crawl_cancel` - `map_create`, `map_get`, `map_list` - `document_parse`, `document_get`, `document_list` Point Claude, Cursor, or any MCP client at `npx @stack0/mcp` with `STACK0_API_KEY` set. ## Workflows API Reference The Workflows API enables you to build multi-step AI pipelines with sequential and parallel execution, variable passing between steps, and webhook notifications. ### Supported Providers - **Anthropic**: Claude models (claude-3-5-sonnet, claude-3-opus, claude-3-haiku) - **OpenAI**: GPT models (gpt-4o, gpt-4-turbo, gpt-3.5-turbo) - **Gemini**: Google AI models (gemini-1.5-pro, gemini-1.5-flash) - **Replicate**: Image/video models (SDXL, Stable Diffusion, etc.) - **Stack0**: Built-in AI capabilities ### Step Types - `llm`: Text generation with AI models - `image`: Image generation (Replicate, DALL-E) - `video`: Video generation (Kling, Replicate) - `audio`: Audio generation (ElevenLabs, etc.) - `code`: Execute custom code - `http`: Make HTTP requests - `transform`: Transform data between steps - `condition`: Conditional branching - `loop`: Iterate over arrays ### Create Workflow ```typescript const workflow = await stack0.workflows.create({ projectSlug: 'my-project', slug: 'content-pipeline', name: 'Content Generation Pipeline', description: 'Generate blog posts with AI', steps: [ { id: 'generate-outline', type: 'llm', name: 'Generate Outline', provider: 'anthropic', model: 'claude-3-5-sonnet-20241022', prompt: 'Create an outline for a blog post about {{topic}}', outputVariable: 'outline', }, { id: 'write-content', type: 'llm', name: 'Write Content', provider: 'openai', model: 'gpt-4o', prompt: 'Write a blog post based on this outline:\n\n{{steps.generate-outline.output}}', dependsOn: ['generate-outline'], outputVariable: 'content', }, ], variables: { topic: { type: 'string', required: true }, }, }); ``` ### List Workflows ```typescript const { workflows, total, hasMore } = await stack0.workflows.list({ projectSlug: 'my-project', limit: 20, offset: 0, }); ``` ### Get Workflow ```typescript const workflow = await stack0.workflows.get({ projectSlug: 'my-project', id: 'workflow_abc123', }); ``` ### Update Workflow ```typescript const workflow = await stack0.workflows.update({ projectSlug: 'my-project', id: 'workflow_abc123', name: 'Updated Pipeline Name', steps: [...updatedSteps], isActive: true, }); ``` ### Delete Workflow ```typescript await stack0.workflows.delete({ projectSlug: 'my-project', id: 'workflow_abc123', }); ``` ### Run Workflow Start a workflow execution: ```typescript const run = await stack0.workflows.run({ projectSlug: 'my-project', workflowId: 'workflow_abc123', variables: { topic: 'AI in Healthcare', }, webhook: { url: 'https://yourapp.com/webhook', secret: 'your-webhook-secret', }, metadata: { userId: 'user_123', source: 'api', }, }); // Response { id: 'run_xyz789', workflowId: 'workflow_abc123', status: 'pending', variables: { topic: 'AI in Healthcare' }, createdAt: Date, } ``` ### Run and Wait For synchronous execution, use `runAndWait`: ```typescript const result = await stack0.workflows.runAndWait({ projectSlug: 'my-project', workflowId: 'workflow_abc123', variables: { topic: 'AI in Healthcare', }, }); // Response includes completed output { id: 'run_xyz789', status: 'completed', output: { outline: '1. Introduction\n2. Current Applications...', content: 'Artificial intelligence is transforming...', }, totalDurationMs: 12500, creditsUsed: 15, } ``` ### Get Run Status ```typescript const run = await stack0.workflows.getRun({ projectSlug: 'my-project', id: 'run_xyz789', }); console.log(`Status: ${run.status}`); console.log(`Progress: ${run.completedSteps}/${run.totalSteps}`); // Check step states for (const [stepId, state] of Object.entries(run.stepStates)) { console.log(`${stepId}: ${state.status}`); } ``` ### List Runs ```typescript const { items, total, hasMore } = await stack0.workflows.listRuns({ projectSlug: 'my-project', workflowId: 'workflow_abc123', // Optional: filter by workflow status: 'completed', // Optional: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled' limit: 50, offset: 0, }); ``` ### Cancel Run ```typescript await stack0.workflows.cancelRun({ projectSlug: 'my-project', id: 'run_xyz789', }); ``` ### Variable Interpolation Use `{{variable}}` syntax to reference variables and step outputs: ```typescript // Reference input variables prompt: 'Write about {{topic}}' // Reference step outputs prompt: 'Expand on: {{steps.generate-outline.output}}' // Reference nested data prompt: 'Use this title: {{steps.generate-metadata.output.title}}' ``` ### Step Configuration Each step type has specific configuration options: #### LLM Step ```typescript { id: 'step-id', type: 'llm', name: 'Generate Text', provider: 'anthropic', // 'anthropic' | 'openai' | 'gemini' model: 'claude-3-5-sonnet-20241022', prompt: 'Your prompt here with {{variables}}', systemPrompt: 'Optional system instructions', temperature: 0.7, // 0-2 maxTokens: 4096, responseFormat: 'text', // 'text' | 'json' dependsOn: ['previous-step-id'], // Step dependencies outputVariable: 'variableName', } ``` #### Image Step ```typescript { id: 'generate-image', type: 'image', name: 'Generate Image', provider: 'replicate', model: 'stability-ai/sdxl', prompt: 'A futuristic city at sunset', width: 1024, height: 1024, outputVariable: 'generatedImage', } ``` #### HTTP Step ```typescript { id: 'fetch-data', type: 'http', name: 'Fetch External Data', method: 'POST', url: 'https://api.example.com/data', headers: { 'Authorization': 'Bearer {{apiKey}}', }, body: { query: '{{searchQuery}}', }, outputVariable: 'externalData', } ``` ### Webhook Events Configure webhooks to receive notifications: - `run.started`: Workflow run started - `run.completed`: Workflow run completed successfully - `run.failed`: Workflow run failed - `step.completed`: Individual step completed Webhook payload: ```json { "event": "run.completed", "timestamp": "2025-01-15T10:30:00Z", "data": { "id": "run_xyz789", "workflowId": "workflow_abc123", "status": "completed", "output": { ... }, "totalDurationMs": 12500, "creditsUsed": 15 } } ``` ### Run Status Flow 1. **pending**: Run created, waiting to start 2. **running**: Workflow executing steps 3. **completed**: All steps completed successfully 4. **failed**: One or more steps failed 5. **cancelled**: Run was cancelled ### Pricing - **LLM Steps**: Based on token usage (varies by provider/model) - **Image Generation**: $0.02-0.10 per image (varies by model) - **Video Generation**: $0.50-2.00 per video (varies by model/duration) - **HTTP Steps**: $0.001 per request ## Domain Verification To send emails from your custom domain, you need to verify domain ownership. ### 1. Add Domain In the dashboard, go to Mail → Domains → Add Domain and enter your domain (e.g., `yourdomain.com`). ### 2. Configure DNS Records Add the provided DNS records to your domain: - **SPF Record**: Authorizes Stack0 to send emails - **DKIM Records**: Cryptographic authentication - **DMARC Record**: Email policy and reporting - **Verification Record**: Proves domain ownership ### 3. Verify Domain Click "Verify Domain" in the dashboard. Verification typically completes within minutes but can take up to 72 hours. ### 4. Set as Default Mark your verified domain as default to use it automatically for all emails. ## Email Tracking Stack0 automatically tracks email engagement: - **Sends**: When email leaves the server - **Deliveries**: When recipient server accepts email - **Opens**: When recipient opens email (requires HTML) - **Clicks**: When recipient clicks links in email - **Bounces**: When email is rejected - **Complaints**: When recipient marks as spam View analytics in the dashboard under Mail → Analytics. ## Common Use Cases ### Transactional Emails Send emails triggered by user actions: ```typescript // Welcome email await stack0.mail.send({ from: 'welcome@yourapp.com', to: newUser.email, subject: 'Welcome to YourApp!', templateId: 'welcome-email', templateVariables: { name: newUser.name }, }); // Password reset await stack0.mail.send({ from: 'noreply@yourapp.com', to: user.email, subject: 'Reset your password', templateId: 'password-reset', templateVariables: { resetUrl: generateResetUrl(user.id), expiresIn: '1 hour', }, }); // Order confirmation await stack0.mail.send({ from: 'orders@yourapp.com', to: customer.email, subject: `Order #${order.id} confirmed`, templateId: 'order-confirmation', templateVariables: { order, customer }, }); ``` ### Marketing Emails Send newsletters and promotional content: ```typescript // Newsletter to subscribers const subscribers = await getActiveSubscribers(); await stack0.mail.sendBroadcast({ from: 'newsletter@yourapp.com', to: subscribers.map(s => s.email), subject: 'Monthly Update - October 2025', templateId: 'monthly-newsletter', tags: ['newsletter', 'october-2025'], }); // Product announcement await stack0.mail.sendBroadcast({ from: 'updates@yourapp.com', to: userSegment.emails, subject: 'Introducing Our New Feature!', html: announcementHtml, tags: ['product-update', 'feature-launch'], }); ``` ### User-Generated Content with CDN Handle user uploads in your application: ```typescript // Upload user avatar const avatar = await stack0.cdn.upload({ projectSlug: 'my-project', file: uploadedFile, filename: `avatar-${userId}.jpg`, mimeType: 'image/jpeg', folder: '/avatars', metadata: { userId }, }); // Get optimized thumbnail (client-side, no API call) const thumbnailUrl = stack0.cdn.getTransformUrl(avatar.cdnUrl, { width: 150, height: 150, fit: 'cover', format: 'webp' }); // Save to user profile await updateUser(userId, { avatarUrl: thumbnailUrl }); ``` ### Product Images Manage e-commerce product images: ```typescript // Upload product image const image = await stack0.cdn.upload({ projectSlug: 'my-project', file: productImage, filename: `product-${productId}-main.jpg`, mimeType: 'image/jpeg', folder: `/products/${productId}`, tags: ['product', productId], metadata: { productId, isPrimary: true }, }); // Generate multiple sizes for responsive images const sizes = [ { width: 1200, name: 'large' }, { width: 800, name: 'medium' }, { width: 400, name: 'small' }, { width: 150, name: 'thumbnail' }, ]; const urls = await Promise.all( sizes.map(async ({ width, name }) => { const { url } = await stack0.cdn.getTransformUrl({ assetId: image.id, options: { width, format: 'webp', quality: 85 }, }); return { name, url }; }) ); ``` ## Error Handling The SDK throws typed errors for different scenarios: ```typescript import { Stack0, Stack0Error } from '@stack0/sdk'; try { await stack0.mail.send({ from: 'invalid@unverified-domain.com', to: 'user@example.com', subject: 'Test', html: '

Test

', }); } catch (error) { if (error instanceof Stack0Error) { console.error('Stack0 Error:', error.message); console.error('Status Code:', error.statusCode); console.error('Error Code:', error.code); } } ``` Common error codes: - `INVALID_FROM`: From address not verified - `INVALID_EMAIL`: Malformed email address - `RATE_LIMIT`: Too many requests - `QUOTA_EXCEEDED`: Usage limit reached - `UNAUTHORIZED`: Invalid API key - `NOT_FOUND`: Resource not found - `PROJECT_NOT_FOUND`: Invalid project slug ## Rate Limits Default rate limits per organization: - **Emails per second**: 10 - **Emails per minute**: 100 - **Emails per hour**: 1,000 - **Emails per day**: 10,000 - **CDN uploads per minute**: 60 - **CDN requests per second**: 100 - **Screenshots per hour**: 100 (Free), 1,000 (Starter), 10,000 (Pro) - **Extractions per hour**: 50 (Free), 500 (Starter), 5,000 (Pro) Contact support for higher limits. Rate limit information is included in response headers: ``` X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1637000000 ``` ## Webhooks Configure webhooks to receive real-time notifications about email events. ### Available Events - `email.sent`: Email sent to mail server - `email.delivered`: Email delivered to recipient - `email.bounced`: Email bounced - `email.opened`: Recipient opened email - `email.clicked`: Recipient clicked link - `email.complained`: Recipient marked as spam ### Setup 1. Go to Mail → Webhooks → Create Webhook 2. Enter your endpoint URL 3. Select events to receive 4. Save and copy the signing secret ### Payload Example ```json { "event": "email.delivered", "timestamp": "2025-11-24T10:30:00Z", "data": { "id": "email_123abc", "from": "noreply@yourdomain.com", "to": "user@example.com", "subject": "Welcome!", "status": "delivered", "deliveredAt": "2025-11-24T10:30:00Z" } } ``` ### Verifying Webhooks Webhooks include a signature header for verification: ```typescript import { createHmac } from 'crypto'; function verifyWebhook(payload: string, signature: string, secret: string) { const hmac = createHmac('sha256', secret); const digest = hmac.update(payload).digest('hex'); return digest === signature; } ``` ## Pricing Usage-based pricing with no monthly fees: - **Emails**: $0.50 per 1,000 emails - **CDN Storage & Bandwidth**: $0.25 per GB - **Image Transformations**: $0.50 per 1,000 transforms - **Video Transcoding**: $0.005 per minute - **Video Streaming**: $0.05 per GB - **Audio Transcoding**: $0.002 per minute - **Integrations**: $1.00 per 1,000 API calls - **Screenshots**: $1.00 per 1,000 screenshots - **AI Extractions**: $2.00 per 1,000 extractions - **Memory Store**: $1.00 per 10,000 memories - **Memory Recall**: $2.00 per 10,000 queries - **Memory Search**: $0.50 per 10,000 queries - **Memory Storage**: $0.25 per GB ### Billing Usage is calculated daily and billed monthly. View current usage in Settings → Billing. ## API Keys Create and manage API keys in Settings → API Keys. ### Types - **Standard**: Full access to all resources in organization - **Send-only**: Can only send emails, cannot read - **Read-only**: Can view data, cannot send or modify ### Security - Store API keys securely (environment variables) - Never commit keys to version control - Rotate keys regularly - Use send-only keys for client-side code - Revoke compromised keys immediately ## Environment Variables Recommended environment variable naming: ```bash # Production STACK0_API_KEY=sk_live_... # Development STACK0_API_KEY=sk_test_... # Optional: Custom API endpoint STACK0_API_URL=https://api.stack0.dev/v1 ``` ## Migration from Other Services ### From Resend Stack0's API is compatible with Resend - just change the import: ```typescript // Before import { Resend } from 'resend'; const resend = new Resend(process.env.RESEND_API_KEY); // After import { Mail as Resend } from '@stack0/sdk/mail'; const resend = new Resend({ apiKey: process.env.STACK0_API_KEY }); ``` ### From SendGrid ```typescript // Before (SendGrid) sgMail.send({ to: 'user@example.com', from: 'noreply@yourdomain.com', subject: 'Hello', html: '

Content

', }); // After (Stack0) stack0.mail.send({ to: 'user@example.com', from: 'noreply@yourdomain.com', subject: 'Hello', html: '

Content

', }); ``` ### From Cloudinary/Imgix ```typescript // Before (Cloudinary) cloudinary.url('sample.jpg', { width: 300, crop: 'fill' }); // After (Stack0) const { url } = await stack0.cdn.getTransformUrl({ assetId: 'asset_abc123', options: { width: 300, fit: 'cover' }, }); ``` ## Dashboard Features Access your dashboard at https://app.stack0.dev ### Mail Dashboard - **Overview**: Key metrics and recent emails - **Logs**: Search and filter all sent emails - **Templates**: Create and manage email templates - **Domains**: Verify and manage sending domains - **Analytics**: Delivery rates, opens, clicks, bounces - **Webhooks**: Configure event notifications - **API Keys**: Generate and manage access keys ### CDN Dashboard - **Assets**: Browse, search, and manage all public CDN assets - **Private Files**: Manage private files with secure download links - **Bundles**: Create and manage zip download bundles - **Folders**: Organize assets with virtual folders - **Usage**: Monitor storage and bandwidth consumption - **Transformations**: View transformation statistics ### Organization Management - **Members**: Invite team members with role-based access - **Billing**: View usage and manage payment methods - **Settings**: Configure organization preferences - **Activity Log**: Audit trail of all actions ## Support - **Documentation**: https://docs.stack0.dev - **Dashboard**: https://app.stack0.dev - **Status**: https://status.stack0.dev - **Email**: support@stack0.dev ## Legal - **Terms of Service**: https://www.stack0.dev/terms - **Privacy Policy**: https://www.stack0.dev/privacy - **Acceptable Use**: https://www.stack0.dev/acceptable-use ## MCP Server Stack0 provides a hosted MCP (Model Context Protocol) server that exposes all Stack0 products as tools for AI agents. Connect Claude Desktop, Cursor, Claude Code, or any MCP-compatible client to access screenshots, extraction, CDN, mail, integrations, workflows, and video tools. ### Connection Configuration ```json { "mcpServers": { "stack0": { "url": "https://api.stack0.dev/mcp/sse", "headers": { "Authorization": "Bearer sk_live_your_api_key" } } } } ``` For Streamable HTTP transport (recommended for Claude Code): ```json { "mcpServers": { "stack0": { "url": "https://api.stack0.dev/mcp", "headers": { "Authorization": "Bearer sk_live_your_api_key" } } } } ``` ### Available Tools #### Screenshots (4 tools) - `screenshots_capture` - Capture webpage screenshot with options for format, viewport, full page - `screenshots_capture_batch` - Capture multiple URLs in parallel - `screenshots_get` - Get screenshot by ID - `screenshots_list` - List screenshots with filters #### Extraction (4 tools) - `extraction_extract` - Extract structured data from URL using AI - `extraction_extract_batch` - Extract from multiple URLs - `extraction_get` - Get extraction by ID - `extraction_list` - List extractions with filters #### CDN (8 tools) - `cdn_upload` - Upload file to CDN - `cdn_get` - Get asset by ID - `cdn_list` - List assets with filters - `cdn_delete` - Delete asset - `cdn_get_transform_url` - Get transformed image URL - `cdn_transcode` - Start video transcode job - `cdn_get_streaming_urls` - Get HLS/MP4 streaming URLs - `cdn_create_folder` - Create folder for organization #### Mail (6 tools) - `mail_send` - Send single email - `mail_send_batch` - Send batch emails (up to 100) - `mail_list` - List sent emails - `mail_get` - Get email by ID - `mail_create_contact` - Create contact - `mail_list_contacts` - List contacts #### Integrations (6 tools) - `integrations_list_connections` - List OAuth connections - `integrations_crm_list_contacts` - List CRM contacts - `integrations_crm_create_contact` - Create CRM contact - `integrations_storage_list_files` - List storage files - `integrations_storage_upload` - Upload to storage - `integrations_communication_send` - Send message #### Workflows (4 tools) - `workflows_create` - Create AI workflow - `workflows_run` - Run workflow - `workflows_get_run` - Get run status - `workflows_list` - List workflows #### Video (4 tools) - `video_transcode` - Start transcode job - `video_get_job` - Get job status - `video_merge` - Create merge job - `video_extract_audio` - Extract audio from video ### MCP Endpoints - `POST /mcp` - Streamable HTTP transport - `GET /mcp/sse` - SSE transport for long-lived connections - `POST /mcp/messages` - SSE message endpoint - `DELETE /mcp` - Close session - `GET /mcp/health` - Health check - `GET /mcp/tools` - List available tools ### Authentication All MCP endpoints require API key authentication via: - `Authorization: Bearer sk_live_...` header - `X-API-Key: sk_live_...` header ### Example: Claude Desktop Add to `~/.config/claude/claude_desktop_config.json`: ```json { "mcpServers": { "stack0": { "url": "https://api.stack0.dev/mcp/sse", "headers": { "Authorization": "Bearer sk_live_your_api_key" } } } } ``` ### Example: Using Tools Once connected, ask Claude to: - "Take a screenshot of example.com" - "Extract product data from this URL" - "Send an email to user@example.com" - "Upload this image to CDN" - "Run my content-pipeline workflow" ## Version Current SDK version: v0.2.9 API version: v1 --- *Stack0 is an AI-native infrastructure platform. This document helps AI assistants understand Stack0's products and guide users in using our services effectively.*