@stack0/sdk 0.5.7 → 0.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Stack0 SDK
2
2
 
3
- Official TypeScript/JavaScript SDK for Stack0 services.
3
+ Official TypeScript/JavaScript SDK for [Stack0](https://stack0.dev) -- a modular platform for email, CDN, screenshots, web extraction, integrations, marketing, and AI workflows.
4
+
5
+ - **Fully typed** -- written in TypeScript with complete type definitions
6
+ - **Zero dependencies** -- uses the native `fetch` API
7
+ - **Tree-shakable** -- import only the modules you need
8
+ - **Isomorphic** -- works in Node.js, Bun, Deno, and edge runtimes
9
+
10
+ [Full API Reference](https://stack0.dev/docs)
4
11
 
5
12
  ## Installation
6
13
 
@@ -20,12 +27,12 @@ bun add @stack0/sdk
20
27
  import { Stack0 } from '@stack0/sdk';
21
28
 
22
29
  const stack0 = new Stack0({
23
- apiKey: 'stack0_...',
30
+ apiKey: process.env.STACK0_API_KEY!,
24
31
  });
25
32
 
26
33
  // Send an email
27
- const email = await stack0.mail.send({
28
- from: 'noreply@example.com',
34
+ await stack0.mail.send({
35
+ from: 'hello@yourapp.com',
29
36
  to: 'user@example.com',
30
37
  subject: 'Welcome!',
31
38
  html: '<h1>Hello World</h1>',
@@ -35,547 +42,686 @@ const email = await stack0.mail.send({
35
42
  const asset = await stack0.cdn.upload({
36
43
  projectSlug: 'my-project',
37
44
  file: fileBuffer,
38
- filename: 'image.jpg',
45
+ filename: 'photo.jpg',
39
46
  mimeType: 'image/jpeg',
40
47
  });
41
48
 
42
- console.log(asset.cdnUrl); // https://cdn.stack0.com/...
49
+ // Capture a screenshot
50
+ const screenshot = await stack0.screenshots.captureAndWait({
51
+ url: 'https://example.com',
52
+ format: 'png',
53
+ fullPage: true,
54
+ });
55
+
56
+ // Extract structured data from a page
57
+ const result = await stack0.extraction.extractAndWait({
58
+ url: 'https://example.com/article',
59
+ mode: 'markdown',
60
+ });
61
+
62
+ // Run an AI workflow
63
+ const run = await stack0.workflows.runAndWait({
64
+ workflowSlug: 'content-pipeline',
65
+ variables: { topic: 'AI trends' },
66
+ });
43
67
  ```
44
68
 
45
69
  ## Configuration
46
70
 
47
71
  ```typescript
48
- import { Stack0 } from '@stack0/sdk';
49
-
50
72
  const stack0 = new Stack0({
51
- apiKey: 'stack0_...', // Required: Your API key
52
- baseUrl: 'https://api.stack0.dev/v1', // Optional: Custom API endpoint
53
- cdnUrl: 'https://cdn.yourproject.stack0.dev', // Optional: For client-side image transforms
73
+ apiKey: 'stack0_...', // Required -- your API key
74
+ baseUrl: 'https://api.stack0.dev/v1', // Optional -- custom API endpoint
75
+ cdnUrl: 'https://cdn.myproject.stack0.dev', // Optional -- for client-side image transforms
54
76
  });
55
77
  ```
56
78
 
57
- ## CDN API
79
+ | Option | Type | Default | Description |
80
+ |--------|------|---------|-------------|
81
+ | `apiKey` | `string` | -- | Your Stack0 API key (required) |
82
+ | `baseUrl` | `string` | `https://api.stack0.dev/v1` | API base URL |
83
+ | `cdnUrl` | `string` | -- | CDN base URL for client-side image transform URLs |
58
84
 
59
- Upload, manage, and transform assets with the CDN API.
85
+ ## Modules
60
86
 
61
- ### Upload File
87
+ The SDK is organized into modules, each accessible as a property on the main `Stack0` client:
62
88
 
63
- ```typescript
64
- // Simple upload (handles presigned URL flow automatically)
65
- const asset = await stack0.cdn.upload({
66
- projectSlug: 'my-project',
67
- file: fileBuffer, // Blob, Buffer, or ArrayBuffer
68
- filename: 'photo.jpg',
69
- mimeType: 'image/jpeg',
70
- folder: '/images/avatars', // Optional
71
- metadata: { userId: 'user_123' }, // Optional
72
- });
89
+ | Module | Accessor | Description |
90
+ |--------|----------|-------------|
91
+ | [Mail](#mail) | `stack0.mail` | Transactional email, campaigns, sequences, contacts |
92
+ | [CDN](#cdn) | `stack0.cdn` | File upload, asset management, image transforms, video |
93
+ | [Screenshots](#screenshots) | `stack0.screenshots` | Webpage screenshot capture |
94
+ | [Extraction](#extraction) | `stack0.extraction` | AI-powered web data extraction |
95
+ | [Integrations](#integrations) | `stack0.integrations` | Unified CRM, storage, communication APIs |
96
+ | [Marketing](#marketing) | `stack0.marketing` | Trend discovery, content generation, scheduling |
97
+ | [Workflows](#workflows) | `stack0.workflows` | AI workflow orchestration |
73
98
 
74
- console.log(asset.id); // Asset ID
75
- console.log(asset.cdnUrl); // CDN URL
76
- console.log(asset.status); // 'ready', 'processing', etc.
77
- ```
99
+ ---
78
100
 
79
- ### Manual Upload Flow
101
+ ## Mail
80
102
 
81
- For more control, use the presigned URL flow:
103
+ Send transactional emails, manage domains, templates, contacts, campaigns, and automated sequences. The Mail API is designed to be compatible with the [Resend](https://resend.com) API for easy migration.
104
+
105
+ ### Sending Emails
82
106
 
83
107
  ```typescript
84
- // 1. Get presigned upload URL
85
- const { uploadUrl, assetId } = await stack0.cdn.getUploadUrl({
86
- projectSlug: 'my-project',
87
- filename: 'document.pdf',
88
- mimeType: 'application/pdf',
89
- size: file.size,
108
+ // Simple email
109
+ const { id } = await stack0.mail.send({
110
+ from: 'hello@yourapp.com',
111
+ to: 'user@example.com',
112
+ subject: 'Welcome!',
113
+ html: '<h1>Hello World</h1>',
90
114
  });
91
115
 
92
- // 2. Upload directly to S3
93
- await fetch(uploadUrl, {
94
- method: 'PUT',
95
- body: file,
96
- headers: { 'Content-Type': 'application/pdf' },
116
+ // With name, CC, BCC, reply-to, and attachments
117
+ await stack0.mail.send({
118
+ from: { name: 'Acme Inc', email: 'noreply@acme.com' },
119
+ to: { name: 'Jane Doe', email: 'jane@example.com' },
120
+ cc: 'manager@example.com',
121
+ bcc: ['audit@example.com'],
122
+ replyTo: 'support@acme.com',
123
+ subject: 'Invoice #1234',
124
+ html: '<p>Your invoice is attached.</p>',
125
+ text: 'Your invoice is attached.',
126
+ tags: ['transactional', 'invoice'],
127
+ metadata: { orderId: '1234' },
128
+ attachments: [
129
+ {
130
+ filename: 'invoice.pdf',
131
+ content: 'base64-encoded-content',
132
+ contentType: 'application/pdf',
133
+ },
134
+ ],
97
135
  });
98
136
 
99
- // 3. Confirm upload
100
- const asset = await stack0.cdn.confirmUpload(assetId);
101
- ```
137
+ // Using a template
138
+ await stack0.mail.send({
139
+ from: 'hello@yourapp.com',
140
+ to: 'user@example.com',
141
+ subject: 'Welcome {{name}}',
142
+ templateId: 'template-uuid',
143
+ templateVariables: { name: 'Jane', activationUrl: 'https://...' },
144
+ });
102
145
 
103
- ### List Assets
146
+ // Batch send (up to 100 emails, each with different content)
147
+ await stack0.mail.sendBatch({
148
+ emails: [
149
+ { from: 'hi@app.com', to: 'user1@example.com', subject: 'Hi', html: '<p>Hello User 1</p>' },
150
+ { from: 'hi@app.com', to: 'user2@example.com', subject: 'Hi', html: '<p>Hello User 2</p>' },
151
+ ],
152
+ });
104
153
 
105
- ```typescript
106
- const { assets, total, hasMore } = await stack0.cdn.list({
107
- projectSlug: 'my-project',
108
- type: 'image', // Optional: 'image', 'video', 'audio', 'document', 'other'
109
- folder: '/images', // Optional
110
- status: 'ready', // Optional
111
- search: 'avatar', // Optional: search in filename
112
- tags: ['profile'], // Optional
113
- sortBy: 'createdAt', // Optional: 'createdAt', 'filename', 'size', 'type'
114
- sortOrder: 'desc', // Optional: 'asc', 'desc'
115
- limit: 20, // Optional
116
- offset: 0, // Optional
117
- });
118
-
119
- for (const asset of assets) {
120
- console.log(asset.filename, asset.cdnUrl);
121
- }
154
+ // Broadcast (same content to up to 1000 recipients)
155
+ await stack0.mail.sendBroadcast({
156
+ from: 'newsletter@app.com',
157
+ to: ['user1@example.com', 'user2@example.com', 'user3@example.com'],
158
+ subject: 'Monthly Update',
159
+ html: '<p>Here is what happened this month...</p>',
160
+ });
122
161
  ```
123
162
 
124
- ### Get Asset
163
+ ### Retrieving and Managing Emails
125
164
 
126
165
  ```typescript
127
- const asset = await stack0.cdn.get('asset-id');
166
+ // Get email details
167
+ const email = await stack0.mail.get('email-id');
168
+ console.log(email.status); // 'delivered', 'bounced', etc.
169
+ console.log(email.openedAt); // Date or null
170
+ console.log(email.deliveredAt); // Date or null
171
+
172
+ // List emails with filters
173
+ const { emails, total } = await stack0.mail.list({
174
+ status: 'delivered',
175
+ from: 'hello@yourapp.com',
176
+ startDate: '2024-01-01',
177
+ sortBy: 'createdAt',
178
+ sortOrder: 'desc',
179
+ limit: 50,
180
+ });
128
181
 
129
- console.log(asset.filename);
130
- console.log(asset.size);
131
- console.log(asset.mimeType);
132
- console.log(asset.width, asset.height); // For images/videos
182
+ // Resend or cancel
183
+ await stack0.mail.resend('email-id');
184
+ await stack0.mail.cancel('scheduled-email-id');
133
185
  ```
134
186
 
135
- ### Update Asset
187
+ ### Analytics
136
188
 
137
189
  ```typescript
138
- const asset = await stack0.cdn.update({
139
- id: 'asset-id',
140
- filename: 'new-name.jpg', // Optional
141
- folder: '/images/archived', // Optional
142
- tags: ['nature', 'sunset'], // Optional
143
- alt: 'A beautiful sunset', // Optional
144
- metadata: { category: 'landscape' }, // Optional
145
- });
190
+ const analytics = await stack0.mail.getAnalytics();
191
+ console.log(`Delivery rate: ${analytics.deliveryRate}%`);
192
+ console.log(`Open rate: ${analytics.openRate}%`);
193
+
194
+ const timeSeries = await stack0.mail.getTimeSeriesAnalytics({ days: 30 });
195
+ const hourly = await stack0.mail.getHourlyAnalytics();
196
+ const senders = await stack0.mail.listSenders({ search: '@yourapp.com' });
146
197
  ```
147
198
 
148
- ### Delete Assets
199
+ ### Sub-clients
149
200
 
150
- ```typescript
151
- // Delete single asset
152
- await stack0.cdn.delete('asset-id');
201
+ The mail module exposes several sub-clients for managing related resources:
153
202
 
154
- // Delete multiple assets
155
- const { deletedCount } = await stack0.cdn.deleteMany([
156
- 'asset-1',
157
- 'asset-2',
158
- 'asset-3',
159
- ]);
203
+ #### Domains
204
+
205
+ ```typescript
206
+ // Add and verify a sending domain
207
+ const { dnsRecords } = await stack0.mail.domains.add({ domain: 'yourapp.com' });
208
+ // Configure DNS records, then verify
209
+ const { verified } = await stack0.mail.domains.verify('domain-id');
210
+
211
+ // List, get DNS records, delete, set default
212
+ const domains = await stack0.mail.domains.list({ projectSlug: 'my-project' });
213
+ const dns = await stack0.mail.domains.getDNSRecords('domain-id');
214
+ await stack0.mail.domains.setDefault('domain-id');
215
+ await stack0.mail.domains.delete('domain-id');
160
216
  ```
161
217
 
162
- ### Move Assets
218
+ #### Templates
163
219
 
164
220
  ```typescript
165
- await stack0.cdn.move({
166
- assetIds: ['asset-1', 'asset-2'],
167
- folder: '/images/archive', // Use null for root folder
221
+ const template = await stack0.mail.templates.create({
222
+ name: 'Welcome Email',
223
+ slug: 'welcome',
224
+ subject: 'Welcome {{name}}!',
225
+ html: '<h1>Hello {{name}}</h1>',
168
226
  });
169
- ```
170
227
 
171
- ### Image Transformations
228
+ const { templates } = await stack0.mail.templates.list({ search: 'welcome' });
229
+ const found = await stack0.mail.templates.getBySlug('welcome');
230
+ const preview = await stack0.mail.templates.preview({ id: template.id, variables: { name: 'Jane' } });
231
+ ```
172
232
 
173
- Generate optimized and transformed image URLs client-side (no API call):
233
+ #### Contacts and Audiences
174
234
 
175
235
  ```typescript
176
- // Using asset's cdnUrl directly (recommended)
177
- const url = stack0.cdn.getTransformUrl(asset.cdnUrl, {
178
- width: 800,
179
- height: 600,
180
- fit: 'cover', // 'cover', 'contain', 'fill', 'inside', 'outside'
181
- format: 'webp', // 'webp', 'jpeg', 'png', 'avif', 'auto'
182
- quality: 80,
236
+ // Create and manage contacts
237
+ const contact = await stack0.mail.contacts.create({
238
+ email: 'user@example.com',
239
+ firstName: 'Jane',
240
+ lastName: 'Doe',
241
+ metadata: { plan: 'pro' },
183
242
  });
184
243
 
185
- // Or using s3Key when cdnUrl is configured in Stack0 options
186
- const url = stack0.cdn.getTransformUrl(asset.s3Key, { width: 400 });
244
+ // Bulk import contacts
245
+ const importResult = await stack0.mail.contacts.import({
246
+ audienceId: 'audience-id',
247
+ contacts: [
248
+ { email: 'user1@example.com', firstName: 'Alice' },
249
+ { email: 'user2@example.com', firstName: 'Bob' },
250
+ ],
251
+ });
187
252
 
188
- // Use in <img> tag
189
- // <img src={url} alt="Optimized image" />
253
+ // Create audiences and add contacts
254
+ const audience = await stack0.mail.audiences.create({
255
+ name: 'Newsletter Subscribers',
256
+ description: 'Users opted in to the newsletter',
257
+ });
258
+ await stack0.mail.audiences.addContacts({ id: audience.id, contactIds: [contact.id] });
190
259
  ```
191
260
 
192
- Advanced transform options:
261
+ #### Campaigns
193
262
 
194
263
  ```typescript
195
- const url = stack0.cdn.getTransformUrl(asset.cdnUrl, {
196
- width: 800,
197
- height: 600,
198
- fit: 'cover',
199
- format: 'webp',
200
- quality: 80,
201
- crop: 'attention', // Smart crop: 'attention', 'entropy', 'center', etc.
202
- blur: 5, // Blur sigma (0.3-100)
203
- sharpen: 1.5, // Sharpen sigma
204
- brightness: 10, // -100 to 100
205
- saturation: -50, // -100 to 100
206
- grayscale: true, // Convert to grayscale
207
- rotate: 90, // 0, 90, 180, 270
208
- flip: true, // Flip vertically
209
- flop: true, // Flip horizontally
264
+ const campaign = await stack0.mail.campaigns.create({
265
+ name: 'Product Launch',
266
+ subject: 'Introducing our new feature!',
267
+ fromEmail: 'hello@yourapp.com',
268
+ audienceId: 'audience-id',
269
+ html: '<p>We are excited to announce...</p>',
210
270
  });
271
+
272
+ await stack0.mail.campaigns.send({ id: campaign.id, sendNow: true });
273
+ const stats = await stack0.mail.campaigns.getStats(campaign.id);
274
+ console.log(`Open rate: ${stats.openRate}%`);
211
275
  ```
212
276
 
213
- ### Folders
277
+ #### Sequences
278
+
279
+ Build automated email flows with a visual node-based editor:
214
280
 
215
281
  ```typescript
216
- // Get folder tree
217
- const tree = await stack0.cdn.getFolderTree({
218
- projectSlug: 'my-project',
219
- maxDepth: 3,
282
+ const sequence = await stack0.mail.sequences.create({
283
+ name: 'Onboarding Flow',
284
+ triggerType: 'contact_added',
285
+ triggerFrequency: 'once',
220
286
  });
221
287
 
222
- // Create folder
223
- const folder = await stack0.cdn.createFolder({
224
- projectSlug: 'my-project',
225
- name: 'avatars',
226
- parentId: 'parent-folder-id', // Optional
288
+ // Add nodes and connections
289
+ const emailNode = await stack0.mail.sequences.createNode({
290
+ id: sequence.id,
291
+ nodeType: 'email',
292
+ name: 'Welcome Email',
293
+ positionX: 200,
294
+ positionY: 100,
295
+ });
296
+
297
+ await stack0.mail.sequences.setNodeEmail(sequence.id, {
298
+ nodeId: emailNode.id,
299
+ subject: 'Welcome!',
300
+ html: '<p>Thanks for signing up.</p>',
227
301
  });
228
302
 
229
- // Delete folder
230
- await stack0.cdn.deleteFolder('folder-id');
231
- await stack0.cdn.deleteFolder('folder-id', true); // Delete with contents
303
+ await stack0.mail.sequences.publish(sequence.id);
232
304
  ```
233
305
 
234
- ### Video Transcoding
306
+ #### Events
235
307
 
236
- Transcode videos into HLS adaptive streaming or MP4 formats for optimal playback.
308
+ Track custom events that can trigger sequences:
237
309
 
238
310
  ```typescript
239
- // Start a transcoding job
240
- const job = await stack0.cdn.transcode({
241
- projectSlug: 'my-project',
242
- assetId: 'video-asset-id',
243
- outputFormat: 'hls', // 'hls' for adaptive streaming, 'mp4' for progressive download
244
- variants: [
245
- { quality: '720p', codec: 'h264' },
246
- { quality: '1080p', codec: 'h264' },
247
- ],
248
- webhookUrl: 'https://your-app.com/webhook', // Optional: get notified when complete
311
+ await stack0.mail.events.track({
312
+ eventName: 'purchase_completed',
313
+ contactEmail: 'user@example.com',
314
+ properties: { orderId: '12345', amount: 99.99 },
249
315
  });
250
316
 
251
- console.log(`Job started: ${job.id}, Status: ${job.status}`);
317
+ // Batch tracking
318
+ await stack0.mail.events.trackBatch({
319
+ events: [
320
+ { eventName: 'page_viewed', contactEmail: 'user1@example.com', properties: { page: '/pricing' } },
321
+ { eventName: 'page_viewed', contactEmail: 'user2@example.com', properties: { page: '/features' } },
322
+ ],
323
+ });
252
324
  ```
253
325
 
254
- ### Check Job Status
326
+ ### Mail Method Reference
327
+
328
+ | Method | Description |
329
+ |--------|-------------|
330
+ | `mail.send(req)` | Send a single email |
331
+ | `mail.sendBatch(req)` | Send up to 100 emails in a batch |
332
+ | `mail.sendBroadcast(req)` | Broadcast to up to 1000 recipients |
333
+ | `mail.get(id)` | Get email by ID |
334
+ | `mail.list(req?)` | List emails with filters |
335
+ | `mail.resend(id)` | Resend an email |
336
+ | `mail.cancel(id)` | Cancel a scheduled email |
337
+ | `mail.getAnalytics()` | Get overall email analytics |
338
+ | `mail.getTimeSeriesAnalytics(req?)` | Get daily analytics breakdown |
339
+ | `mail.getHourlyAnalytics()` | Get hourly analytics |
340
+ | `mail.listSenders(req?)` | List unique senders with stats |
341
+ | `mail.domains.*` | Domain management (list, add, verify, delete, setDefault, getDNSRecords) |
342
+ | `mail.templates.*` | Template CRUD + preview and getBySlug |
343
+ | `mail.audiences.*` | Audience CRUD + addContacts, removeContacts, listContacts |
344
+ | `mail.contacts.*` | Contact CRUD + import |
345
+ | `mail.campaigns.*` | Campaign CRUD + send, pause, cancel, duplicate, getStats |
346
+ | `mail.sequences.*` | Sequence CRUD + node/connection management + publish/pause/resume/archive |
347
+ | `mail.events.*` | Event CRUD + track, trackBatch, listOccurrences, getAnalytics |
348
+
349
+ ---
350
+
351
+ ## CDN
352
+
353
+ Upload, manage, and transform files. Supports images, video transcoding, private files, download bundles, and S3 imports.
354
+
355
+ ### Uploading Files
255
356
 
256
357
  ```typescript
257
- // Get job by ID
258
- const job = await stack0.cdn.getJob('job-id');
259
- console.log(`Status: ${job.status}, Progress: ${job.progress}%`);
358
+ // Simple upload (handles presigned URL flow automatically)
359
+ const asset = await stack0.cdn.upload({
360
+ projectSlug: 'my-project',
361
+ file: fileBuffer, // Blob, Buffer, or ArrayBuffer
362
+ filename: 'photo.jpg',
363
+ mimeType: 'image/jpeg',
364
+ folder: '/images/avatars',
365
+ metadata: { userId: 'user_123' },
366
+ });
260
367
 
261
- // List all jobs
262
- const { jobs, total } = await stack0.cdn.listJobs({
368
+ console.log(asset.id); // Asset ID
369
+ console.log(asset.cdnUrl); // Public CDN URL
370
+
371
+ // Manual presigned URL flow for more control
372
+ const { uploadUrl, assetId } = await stack0.cdn.getUploadUrl({
263
373
  projectSlug: 'my-project',
264
- status: 'processing', // Optional filter
265
- limit: 20,
374
+ filename: 'document.pdf',
375
+ mimeType: 'application/pdf',
376
+ size: file.size,
266
377
  });
267
378
 
268
- // Cancel a pending/processing job
269
- await stack0.cdn.cancelJob('job-id');
379
+ await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': 'application/pdf' } });
380
+ const confirmed = await stack0.cdn.confirmUpload(assetId);
270
381
  ```
271
382
 
272
- ### Get Streaming URLs
383
+ ### Asset Management
273
384
 
274
385
  ```typescript
275
- const urls = await stack0.cdn.getStreamingUrls('asset-id');
276
-
277
- // HLS URL for adaptive streaming (recommended)
278
- console.log(`HLS Master Playlist: ${urls.hlsUrl}`);
386
+ // Get, update, delete
387
+ const asset = await stack0.cdn.get('asset-id');
388
+ await stack0.cdn.update({ id: 'asset-id', alt: 'Sunset photo', tags: ['nature'] });
389
+ await stack0.cdn.delete('asset-id');
390
+ await stack0.cdn.deleteMany(['asset-1', 'asset-2']);
279
391
 
280
- // MP4 URLs for direct download
281
- for (const mp4 of urls.mp4Urls) {
282
- console.log(`${mp4.quality}: ${mp4.url}`);
283
- }
392
+ // List with filters
393
+ const { assets, total, hasMore } = await stack0.cdn.list({
394
+ projectSlug: 'my-project',
395
+ type: 'image',
396
+ folder: '/images',
397
+ search: 'avatar',
398
+ sortBy: 'createdAt',
399
+ sortOrder: 'desc',
400
+ limit: 20,
401
+ });
284
402
 
285
- // Thumbnails
286
- for (const thumb of urls.thumbnails) {
287
- console.log(`Thumbnail at ${thumb.timestamp}s: ${thumb.url}`);
288
- }
403
+ // Move assets between folders
404
+ await stack0.cdn.move({ assetIds: ['asset-1', 'asset-2'], folder: '/archive' });
289
405
  ```
290
406
 
291
- ### Generate Thumbnails
407
+ ### Image Transformations
408
+
409
+ Generate optimized image URLs client-side (no API call required):
292
410
 
293
411
  ```typescript
294
- const thumbnail = await stack0.cdn.getThumbnail({
295
- assetId: 'video-asset-id',
296
- timestamp: 10.5, // 10.5 seconds into the video
297
- width: 320, // Optional: resize
298
- format: 'webp', // 'jpg', 'png', 'webp'
412
+ const url = stack0.cdn.getTransformUrl(asset.cdnUrl, {
413
+ width: 800,
414
+ height: 600,
415
+ fit: 'cover',
416
+ format: 'webp',
417
+ quality: 80,
299
418
  });
300
419
 
301
- console.log(`Thumbnail URL: ${thumbnail.url}`);
302
- ```
303
-
304
- ### Extract Audio
305
-
306
- ```typescript
307
- const { jobId, status } = await stack0.cdn.extractAudio({
308
- projectSlug: 'my-project',
309
- assetId: 'video-asset-id',
310
- format: 'mp3', // 'mp3', 'aac', 'wav'
311
- bitrate: 192, // Optional: kbps
420
+ // Advanced transforms
421
+ const advancedUrl = stack0.cdn.getTransformUrl(asset.cdnUrl, {
422
+ width: 400,
423
+ crop: 'attention', // Smart crop: 'attention', 'entropy', 'center'
424
+ blur: 5,
425
+ sharpen: 1.5,
426
+ brightness: 10,
427
+ saturation: -50,
428
+ grayscale: true,
429
+ rotate: 90,
430
+ flip: true,
431
+ flop: true,
312
432
  });
313
433
  ```
314
434
 
315
- ### Video Playback with HLS.js
316
-
317
- ```typescript
318
- import Hls from 'hls.js';
435
+ | Transform | Type | Description |
436
+ |-----------|------|-------------|
437
+ | `width` | `number` | Target width (snapped to nearest allowed width for caching) |
438
+ | `height` | `number` | Target height |
439
+ | `fit` | `string` | `cover`, `contain`, `fill`, `inside`, `outside` |
440
+ | `format` | `string` | `webp`, `jpeg`, `png`, `avif`, `auto` |
441
+ | `quality` | `number` | Output quality (1-100) |
442
+ | `crop` | `string` | Smart crop strategy: `attention`, `entropy`, `center` |
443
+ | `blur` | `number` | Gaussian blur sigma (0.3-100) |
444
+ | `sharpen` | `number` | Sharpen sigma |
445
+ | `brightness` | `number` | -100 to 100 |
446
+ | `saturation` | `number` | -100 to 100 |
447
+ | `grayscale` | `boolean` | Convert to grayscale |
448
+ | `rotate` | `number` | 0, 90, 180, 270 |
449
+ | `flip` | `boolean` | Flip vertically |
450
+ | `flop` | `boolean` | Flip horizontally |
319
451
 
320
- const urls = await stack0.cdn.getStreamingUrls('asset-id');
452
+ ### Folders
321
453
 
322
- const video = document.getElementById('video');
323
- if (Hls.isSupported() && urls.hlsUrl) {
324
- const hls = new Hls();
325
- hls.loadSource(urls.hlsUrl);
326
- hls.attachMedia(video);
327
- } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
328
- // Safari native HLS support
329
- video.src = urls.hlsUrl;
330
- }
454
+ ```typescript
455
+ const tree = await stack0.cdn.getFolderTree({ projectSlug: 'my-project', maxDepth: 3 });
456
+ const folder = await stack0.cdn.createFolder({ projectSlug: 'my-project', name: 'avatars' });
457
+ const byPath = await stack0.cdn.getFolderByPath('/images/avatars');
458
+ await stack0.cdn.updateFolder({ id: folder.id, name: 'profile-pictures' });
459
+ await stack0.cdn.moveFolder({ id: folder.id, newParentId: 'other-folder-id' });
460
+ await stack0.cdn.deleteFolder(folder.id, true); // true = delete contents
331
461
  ```
332
462
 
333
- ## Mail API
334
-
335
- The Mail API is compatible with the Resend API for easy migration.
336
-
337
- ### Send Email
463
+ ### Video Transcoding
338
464
 
339
465
  ```typescript
340
- // Simple email
341
- await stack0.mail.send({
342
- from: 'hello@example.com',
343
- to: 'user@example.com',
344
- subject: 'Hello',
345
- html: '<p>Welcome to Stack0!</p>',
346
- });
347
-
348
- // With multiple recipients
349
- await stack0.mail.send({
350
- from: 'hello@example.com',
351
- to: ['user1@example.com', 'user2@example.com'],
352
- subject: 'Newsletter',
353
- html: '<p>Monthly update</p>',
354
- });
355
-
356
- // With name and email
357
- await stack0.mail.send({
358
- from: { name: 'Acme Inc', email: 'noreply@acme.com' },
359
- to: { name: 'John Doe', email: 'john@example.com' },
360
- subject: 'Important Update',
361
- html: '<p>Your account has been updated</p>',
362
- });
363
-
364
- // With CC and BCC
365
- await stack0.mail.send({
366
- from: 'hello@example.com',
367
- to: 'user@example.com',
368
- cc: 'manager@example.com',
369
- bcc: ['audit@example.com', 'compliance@example.com'],
370
- subject: 'Team Update',
371
- html: '<p>Quarterly results</p>',
466
+ // Start a transcoding job
467
+ const job = await stack0.cdn.transcode({
468
+ projectSlug: 'my-project',
469
+ assetId: 'video-asset-id',
470
+ outputFormat: 'hls',
471
+ variants: [
472
+ { quality: '720p', codec: 'h264' },
473
+ { quality: '1080p', codec: 'h264' },
474
+ ],
475
+ webhookUrl: 'https://your-app.com/webhook',
372
476
  });
373
477
 
374
- // With plain text alternative
375
- await stack0.mail.send({
376
- from: 'hello@example.com',
377
- to: 'user@example.com',
378
- subject: 'Hello',
379
- html: '<h1>Welcome!</h1><p>Thanks for joining</p>',
380
- text: 'Welcome! Thanks for joining',
381
- });
478
+ // Check job status
479
+ const status = await stack0.cdn.getJob(job.id);
480
+ console.log(`Progress: ${status.progress}%`);
382
481
 
383
- // With tags and metadata
384
- await stack0.mail.send({
385
- from: 'hello@example.com',
386
- to: 'user@example.com',
387
- subject: 'Order Confirmation',
388
- html: '<p>Your order #12345 is confirmed</p>',
389
- tags: ['transactional', 'order'],
390
- metadata: {
391
- orderId: '12345',
392
- customerId: 'cus_abc123',
393
- },
394
- });
482
+ // Get streaming URLs for playback
483
+ const urls = await stack0.cdn.getStreamingUrls('asset-id');
484
+ console.log(urls.hlsUrl);
395
485
 
396
- // With template
397
- await stack0.mail.send({
398
- from: 'hello@example.com',
399
- to: 'user@example.com',
400
- subject: 'Welcome {{name}}',
401
- templateId: 'template-uuid',
402
- templateVariables: {
403
- name: 'John',
404
- activationUrl: 'https://example.com/activate?token=...',
405
- },
486
+ // Generate thumbnails
487
+ const thumb = await stack0.cdn.getThumbnail({
488
+ assetId: 'video-asset-id',
489
+ timestamp: 10.5,
490
+ width: 320,
491
+ format: 'webp',
406
492
  });
407
493
 
408
- // With attachments
409
- await stack0.mail.send({
410
- from: 'hello@example.com',
411
- to: 'user@example.com',
412
- subject: 'Invoice',
413
- html: '<p>Your invoice is attached</p>',
414
- attachments: [
415
- {
416
- filename: 'invoice.pdf',
417
- content: 'base64-encoded-content',
418
- contentType: 'application/pdf',
419
- },
420
- ],
494
+ // Extract audio
495
+ const { jobId } = await stack0.cdn.extractAudio({
496
+ projectSlug: 'my-project',
497
+ assetId: 'video-asset-id',
498
+ format: 'mp3',
499
+ bitrate: 192,
421
500
  });
422
501
  ```
423
502
 
424
- ### Get Email
425
-
426
- Retrieve details about a sent email:
503
+ ### GIF Generation
427
504
 
428
505
  ```typescript
429
- const email = await stack0.mail.get('email-id-uuid');
506
+ const gif = await stack0.cdn.generateGif({
507
+ projectSlug: 'my-project',
508
+ assetId: 'video-asset-id',
509
+ startTime: 5,
510
+ duration: 3,
511
+ width: 480,
512
+ fps: 10,
513
+ });
430
514
 
431
- console.log(email.status); // 'sent', 'delivered', 'bounced', etc.
432
- console.log(email.openedAt); // Date or null
433
- console.log(email.deliveredAt); // Date or null
515
+ // Poll for completion
516
+ let result = await stack0.cdn.getGif(gif.id);
517
+ while (result?.status === 'pending' || result?.status === 'processing') {
518
+ await new Promise(r => setTimeout(r, 1000));
519
+ result = await stack0.cdn.getGif(gif.id);
520
+ }
521
+ console.log(result?.url);
434
522
  ```
435
523
 
436
- ## Error Handling
524
+ ### Video Merge
437
525
 
438
526
  ```typescript
439
- try {
440
- await stack0.mail.send({
441
- from: 'hello@example.com',
442
- to: 'user@example.com',
443
- subject: 'Test',
444
- html: '<p>Test</p>',
445
- });
446
- } catch (error) {
447
- if (error.statusCode === 401) {
448
- console.error('Invalid API key');
449
- } else if (error.statusCode === 403) {
450
- console.error('Insufficient permissions');
451
- } else if (error.statusCode === 400) {
452
- console.error('Validation error:', error.message);
453
- } else {
454
- console.error('Error:', error.message);
455
- }
456
- }
527
+ const mergeJob = await stack0.cdn.createMergeJob({
528
+ projectSlug: 'my-project',
529
+ inputs: [
530
+ { assetId: 'intro-video-id' },
531
+ { assetId: 'image-id', duration: 5 },
532
+ { assetId: 'main-video-id', startTime: 10, endTime: 60 },
533
+ ],
534
+ audioTrack: { assetId: 'music-id', loop: true, fadeIn: 2, fadeOut: 3 },
535
+ output: { format: 'mp4', quality: '1080p', filename: 'final.mp4' },
536
+ });
457
537
  ```
458
538
 
459
- ## TypeScript Support
539
+ ### Private Files
460
540
 
461
- The SDK is written in TypeScript and includes full type definitions:
541
+ Private files are stored securely and can only be accessed through authorized, time-limited download URLs.
462
542
 
463
543
  ```typescript
464
- import type {
465
- // Mail types
466
- SendEmailRequest,
467
- SendEmailResponse,
468
- // CDN types
469
- Asset,
470
- UploadUrlRequest,
471
- ListAssetsRequest,
472
- TransformOptions,
473
- } from '@stack0/sdk';
474
- ```
544
+ // Upload a private file
545
+ const privateFile = await stack0.cdn.uploadPrivate({
546
+ projectSlug: 'my-project',
547
+ file: fileBuffer,
548
+ filename: 'confidential.pdf',
549
+ mimeType: 'application/pdf',
550
+ });
475
551
 
476
- ## Environment Variables
552
+ // Generate a time-limited download URL
553
+ const { downloadUrl, expiresAt } = await stack0.cdn.getPrivateDownloadUrl({
554
+ fileId: privateFile.id,
555
+ expiresIn: 86400, // 24 hours in seconds
556
+ });
557
+ ```
477
558
 
478
- For security, store your API key in environment variables:
559
+ ### Download Bundles
479
560
 
480
561
  ```typescript
481
- const stack0 = new Stack0({
482
- apiKey: process.env.STACK0_API_KEY!,
562
+ const { bundle } = await stack0.cdn.createBundle({
563
+ projectSlug: 'my-project',
564
+ name: 'December Assets',
565
+ assetIds: ['asset-1', 'asset-2'],
566
+ privateFileIds: ['file-1'],
567
+ expiresIn: 86400,
483
568
  });
484
- ```
485
569
 
486
- ## Migration from Resend
570
+ const { downloadUrl } = await stack0.cdn.getBundleDownloadUrl({
571
+ bundleId: bundle.id,
572
+ expiresIn: 3600,
573
+ });
574
+ ```
487
575
 
488
- Stack0 Mail API is designed to be compatible with Resend:
576
+ ### Usage Tracking
489
577
 
490
578
  ```typescript
491
- // Before (Resend)
492
- import { Resend } from 'resend';
493
- const resend = new Resend('re_...');
494
- await resend.emails.send({ ... });
579
+ const usage = await stack0.cdn.getUsage({ projectSlug: 'my-project' });
580
+ console.log(`Bandwidth: ${usage.bandwidthFormatted}`);
495
581
 
496
- // After (Stack0)
497
- import { Stack0 } from '@stack0/sdk';
498
- const stack0 = new Stack0({ apiKey: 'stack0_...' });
499
- await stack0.mail.send({ ... });
582
+ const history = await stack0.cdn.getUsageHistory({ projectSlug: 'my-project', days: 30 });
583
+ const breakdown = await stack0.cdn.getStorageBreakdown({ projectSlug: 'my-project', groupBy: 'type' });
500
584
  ```
501
585
 
502
- ## Screenshots API
586
+ ---
503
587
 
504
- Capture high-quality screenshots of any webpage.
588
+ ## Screenshots
505
589
 
506
- ### Basic Screenshot
590
+ Capture high-quality screenshots of any webpage with options for format, viewport, device emulation, and more.
591
+
592
+ ### Basic Capture
507
593
 
508
594
  ```typescript
509
- // Capture a screenshot and wait for completion
595
+ // Capture and wait for the result (recommended for most use cases)
510
596
  const screenshot = await stack0.screenshots.captureAndWait({
511
597
  url: 'https://example.com',
512
598
  format: 'png',
513
599
  fullPage: true,
514
600
  blockAds: true,
601
+ blockCookieBanners: true,
515
602
  });
516
603
 
517
604
  console.log(screenshot.imageUrl);
518
605
  console.log(screenshot.imageWidth, screenshot.imageHeight);
519
606
  ```
520
607
 
521
- ### Screenshot Options
608
+ ### Capture Options
522
609
 
523
610
  ```typescript
524
611
  const screenshot = await stack0.screenshots.captureAndWait({
525
612
  url: 'https://example.com',
526
- format: 'png', // 'png' | 'jpeg' | 'webp' | 'pdf'
527
- quality: 90, // JPEG/WebP quality
528
- fullPage: true,
529
- deviceType: 'desktop', // 'desktop' | 'tablet' | 'mobile'
613
+ format: 'png', // 'png' | 'jpeg' | 'webp' | 'pdf'
614
+ quality: 90, // JPEG/WebP quality (1-100)
615
+ fullPage: true, // Capture entire scrollable page
616
+ deviceType: 'desktop', // 'desktop' | 'tablet' | 'mobile'
530
617
  viewportWidth: 1280,
531
618
  viewportHeight: 720,
532
- // Block unwanted elements
533
619
  blockAds: true,
534
620
  blockCookieBanners: true,
535
621
  blockChatWidgets: true,
536
- // Wait for content
537
622
  waitForSelector: '.main-content',
538
- waitForTimeout: 2000,
539
- // Custom headers/cookies for auth
623
+ waitForTimeout: 2000, // Wait ms after page load
624
+ darkMode: true,
625
+ hideSelectors: ['.popup', '.banner'],
626
+ customCss: 'body { background: white; }',
627
+ customJs: 'document.querySelector(".modal")?.remove()',
540
628
  headers: { 'Authorization': 'Bearer token' },
541
629
  cookies: [{ name: 'session', value: 'abc123' }],
630
+ clip: { x: 0, y: 0, width: 800, height: 600 },
631
+ cacheKey: 'homepage-v2',
632
+ cacheTtl: 3600,
633
+ webhookUrl: 'https://your-app.com/webhook',
634
+ webhookSecret: 'secret',
542
635
  });
543
636
  ```
544
637
 
638
+ ### Async Capture (Manual Polling)
639
+
640
+ ```typescript
641
+ const { id } = await stack0.screenshots.capture({
642
+ url: 'https://example.com',
643
+ format: 'png',
644
+ });
645
+
646
+ // Poll for completion
647
+ const screenshot = await stack0.screenshots.get({ id });
648
+ if (screenshot.status === 'completed') {
649
+ console.log(screenshot.imageUrl);
650
+ }
651
+ ```
652
+
545
653
  ### Batch Screenshots
546
654
 
547
655
  ```typescript
656
+ // Capture multiple URLs and wait for all to complete
548
657
  const job = await stack0.screenshots.batchAndWait({
549
658
  urls: ['https://example.com', 'https://example.org'],
550
659
  config: { format: 'png', fullPage: true },
551
660
  });
552
661
 
553
- console.log(`Success: ${job.successfulUrls}, Failed: ${job.failedUrls}`);
662
+ console.log(`Processed: ${job.processedUrls}/${job.totalUrls}`);
663
+ ```
664
+
665
+ ### Scheduled Screenshots
666
+
667
+ ```typescript
668
+ const { id } = await stack0.screenshots.createSchedule({
669
+ name: 'Daily homepage capture',
670
+ url: 'https://example.com',
671
+ frequency: 'daily',
672
+ config: { format: 'png', fullPage: true },
673
+ });
674
+
675
+ await stack0.screenshots.toggleSchedule({ id }); // Toggle on/off
554
676
  ```
555
677
 
556
- ## Extraction API
678
+ ### Screenshots Method Reference
679
+
680
+ | Method | Description |
681
+ |--------|-------------|
682
+ | `capture(req)` | Start a screenshot capture |
683
+ | `get(req)` | Get screenshot by ID |
684
+ | `list(req?)` | List screenshots with filters |
685
+ | `delete(req)` | Delete a screenshot |
686
+ | `captureAndWait(req, opts?)` | Capture and poll until complete |
687
+ | `batch(req)` | Start a batch capture job |
688
+ | `getBatchJob(req)` | Get batch job status |
689
+ | `listBatchJobs(req?)` | List batch jobs |
690
+ | `cancelBatchJob(req)` | Cancel a batch job |
691
+ | `batchAndWait(req, opts?)` | Batch capture and poll until complete |
692
+ | `createSchedule(req)` | Create a recurring capture schedule |
693
+ | `updateSchedule(req)` | Update a schedule |
694
+ | `getSchedule(req)` | Get a schedule |
695
+ | `listSchedules(req?)` | List schedules |
696
+ | `deleteSchedule(req)` | Delete a schedule |
697
+ | `toggleSchedule(req)` | Toggle a schedule on/off |
698
+
699
+ ---
700
+
701
+ ## Extraction
557
702
 
558
- Extract structured data from any webpage using AI.
703
+ Extract structured data from any webpage using AI. Supports markdown, JSON schema, and raw HTML extraction modes.
559
704
 
560
- ### Basic Extraction
705
+ ### Markdown Extraction
561
706
 
562
707
  ```typescript
563
- // Extract content as markdown
564
- const extraction = await stack0.extraction.extractAndWait({
708
+ const result = await stack0.extraction.extractAndWait({
565
709
  url: 'https://example.com/article',
566
710
  mode: 'markdown',
567
711
  includeMetadata: true,
568
712
  });
569
713
 
570
- console.log(extraction.markdown);
571
- console.log(extraction.pageMetadata?.title);
714
+ console.log(result.markdown);
715
+ console.log(result.pageMetadata?.title);
716
+ console.log(result.pageMetadata?.description);
572
717
  ```
573
718
 
574
719
  ### Schema-Based Extraction
575
720
 
721
+ Extract structured data that matches a JSON schema:
722
+
576
723
  ```typescript
577
- // Extract structured data using a JSON schema
578
- const extraction = await stack0.extraction.extractAndWait({
724
+ const result = await stack0.extraction.extractAndWait({
579
725
  url: 'https://example.com/product',
580
726
  mode: 'schema',
581
727
  schema: {
@@ -584,28 +730,539 @@ const extraction = await stack0.extraction.extractAndWait({
584
730
  name: { type: 'string' },
585
731
  price: { type: 'number' },
586
732
  description: { type: 'string' },
733
+ inStock: { type: 'boolean' },
587
734
  },
588
735
  required: ['name', 'price'],
589
736
  },
590
737
  });
591
738
 
592
- console.log(extraction.extractedData);
593
- // { name: 'Product Name', price: 29.99, description: '...' }
739
+ console.log(result.extractedData);
740
+ // { name: 'Product X', price: 29.99, description: '...', inStock: true }
594
741
  ```
595
742
 
596
743
  ### Extraction Modes
597
744
 
598
- - `markdown`: AI-cleaned markdown content
599
- - `schema`: Structured data matching your schema
600
- - `auto`: AI determines best extraction
601
- - `raw`: Raw HTML content
745
+ | Mode | Description |
746
+ |------|-------------|
747
+ | `markdown` | AI-cleaned markdown content |
748
+ | `schema` | Structured data matching a provided JSON schema |
749
+ | `auto` | AI determines the best extraction strategy |
750
+ | `raw` | Raw HTML content |
751
+
752
+ ### Batch Extraction
753
+
754
+ ```typescript
755
+ const job = await stack0.extraction.batchAndWait({
756
+ urls: [
757
+ 'https://example.com/article-1',
758
+ 'https://example.com/article-2',
759
+ ],
760
+ config: { mode: 'markdown' },
761
+ });
762
+ ```
763
+
764
+ ### Scheduled Extraction
765
+
766
+ ```typescript
767
+ const { id } = await stack0.extraction.createSchedule({
768
+ name: 'Daily price monitor',
769
+ url: 'https://competitor.com/pricing',
770
+ frequency: 'daily',
771
+ config: { mode: 'schema', schema: { /* ... */ } },
772
+ });
773
+ ```
774
+
775
+ ### Usage Stats
776
+
777
+ ```typescript
778
+ const usage = await stack0.extraction.getUsage({
779
+ periodStart: '2024-01-01T00:00:00Z',
780
+ periodEnd: '2024-01-31T23:59:59Z',
781
+ });
782
+ console.log(`Extractions: ${usage.extractionsTotal}`);
783
+ console.log(`Tokens used: ${usage.extractionTokensUsed}`);
784
+
785
+ const daily = await stack0.extraction.getUsageDaily({ /* ... */ });
786
+ ```
787
+
788
+ ### Extraction Method Reference
789
+
790
+ | Method | Description |
791
+ |--------|-------------|
792
+ | `extract(req)` | Start an extraction |
793
+ | `get(req)` | Get extraction by ID |
794
+ | `list(req?)` | List extractions |
795
+ | `delete(req)` | Delete an extraction |
796
+ | `extractAndWait(req, opts?)` | Extract and poll until complete |
797
+ | `batch(req)` | Start a batch extraction |
798
+ | `getBatchJob(req)` | Get batch job status |
799
+ | `listBatchJobs(req?)` | List batch jobs |
800
+ | `cancelBatchJob(req)` | Cancel a batch job |
801
+ | `batchAndWait(req, opts?)` | Batch extract and poll until complete |
802
+ | `createSchedule(req)` | Create a recurring schedule |
803
+ | `updateSchedule(req)` | Update a schedule |
804
+ | `getSchedule(req)` | Get a schedule |
805
+ | `listSchedules(req?)` | List schedules |
806
+ | `deleteSchedule(req)` | Delete a schedule |
807
+ | `toggleSchedule(req)` | Toggle a schedule on/off |
808
+ | `getUsage(req?)` | Get usage statistics |
809
+ | `getUsageDaily(req?)` | Get daily usage breakdown |
810
+
811
+ ---
812
+
813
+ ## Integrations
814
+
815
+ Unified API for connecting to third-party services. Manage OAuth connections and interact with CRM, storage, communication, and productivity platforms through a single interface.
816
+
817
+ ### Managing Connections
818
+
819
+ ```typescript
820
+ // Initiate OAuth flow
821
+ const { authUrl, connectionId } = await stack0.integrations.initiateOAuth({
822
+ connectorSlug: 'hubspot',
823
+ redirectUrl: 'https://yourapp.com/oauth/callback',
824
+ name: 'My HubSpot',
825
+ });
826
+ // Redirect user to authUrl...
827
+
828
+ // Complete OAuth after callback
829
+ await stack0.integrations.completeOAuth({
830
+ code: 'auth_code_from_callback',
831
+ state: 'state_from_initiate',
832
+ redirectUrl: 'https://yourapp.com/oauth/callback',
833
+ });
834
+
835
+ // List and manage connections
836
+ const { connections } = await stack0.integrations.listConnections({
837
+ status: 'connected',
838
+ });
839
+
840
+ const stats = await stack0.integrations.getStats({ environment: 'production' });
841
+ console.log(`Active connections: ${stats.activeConnections}`);
842
+ ```
843
+
844
+ ### CRM
845
+
846
+ ```typescript
847
+ // Contacts
848
+ const contacts = await stack0.integrations.crm.listContacts('conn_123');
849
+ const contact = await stack0.integrations.crm.createContact('conn_123', {
850
+ firstName: 'Jane',
851
+ lastName: 'Doe',
852
+ email: 'jane@example.com',
853
+ });
854
+
855
+ // Companies
856
+ const companies = await stack0.integrations.crm.listCompanies('conn_123');
857
+
858
+ // Deals
859
+ const deals = await stack0.integrations.crm.listDeals('conn_123');
860
+ const deal = await stack0.integrations.crm.createDeal('conn_123', {
861
+ name: 'Enterprise Contract',
862
+ amount: 50000,
863
+ stage: 'negotiation',
864
+ });
865
+ ```
866
+
867
+ ### Storage
868
+
869
+ ```typescript
870
+ // Files
871
+ const files = await stack0.integrations.storage.listFiles('conn_456', 'folder-id');
872
+ await stack0.integrations.storage.uploadFile('conn_456', {
873
+ name: 'report.pdf',
874
+ mimeType: 'application/pdf',
875
+ data: fileBuffer,
876
+ folderId: 'folder-id',
877
+ });
878
+ const { data, filename } = await stack0.integrations.storage.downloadFile('conn_456', 'file-id');
879
+
880
+ // Folders
881
+ const folders = await stack0.integrations.storage.listFolders('conn_456');
882
+ await stack0.integrations.storage.createFolder('conn_456', { name: 'Reports', parentId: 'root' });
883
+ ```
884
+
885
+ ### Communication
886
+
887
+ ```typescript
888
+ // Send a message via Slack, Teams, etc.
889
+ await stack0.integrations.communication.sendMessage('conn_789', {
890
+ channelId: 'C123',
891
+ content: 'Hello from Stack0!',
892
+ });
893
+
894
+ const channels = await stack0.integrations.communication.listChannels('conn_789');
895
+ const messages = await stack0.integrations.communication.listMessages('conn_789', 'C123');
896
+ const users = await stack0.integrations.communication.listUsers('conn_789');
897
+ ```
898
+
899
+ ### Productivity
900
+
901
+ ```typescript
902
+ // Documents (Notion, Google Docs, etc.)
903
+ const docs = await stack0.integrations.productivity.listDocuments('conn_abc');
904
+ await stack0.integrations.productivity.createDocument('conn_abc', {
905
+ title: 'Meeting Notes',
906
+ content: 'Agenda: ...',
907
+ });
908
+
909
+ // Tables (Airtable, Google Sheets, etc.)
910
+ const tables = await stack0.integrations.productivity.listTables('conn_abc');
911
+ const rows = await stack0.integrations.productivity.listTableRows('conn_abc', 'table-id');
912
+ await stack0.integrations.productivity.createTableRow('conn_abc', 'table-id', {
913
+ fields: { Name: 'Alice', Email: 'alice@example.com' },
914
+ });
915
+ ```
916
+
917
+ ---
918
+
919
+ ## Marketing
920
+
921
+ AI-powered trend discovery, content opportunity generation, script creation, and content calendar management.
922
+
923
+ ### Discovering Trends
924
+
925
+ ```typescript
926
+ const { trendsDiscovered, trends } = await stack0.marketing.discoverTrends({
927
+ projectSlug: 'my-project',
928
+ environment: 'production',
929
+ });
930
+ console.log(`Discovered ${trendsDiscovered} new trends`);
931
+
932
+ const allTrends = await stack0.marketing.listTrends({
933
+ projectSlug: 'my-project',
934
+ environment: 'production',
935
+ status: 'active',
936
+ });
937
+ ```
938
+
939
+ ### Generating Content Opportunities
940
+
941
+ ```typescript
942
+ const { opportunitiesGenerated } = await stack0.marketing.generateOpportunities({
943
+ projectSlug: 'my-project',
944
+ environment: 'production',
945
+ });
946
+
947
+ const opportunities = await stack0.marketing.listOpportunities({
948
+ projectSlug: 'my-project',
949
+ environment: 'production',
950
+ status: 'pending',
951
+ });
952
+ ```
953
+
954
+ ### Content and Scripts
955
+
956
+ ```typescript
957
+ // Create content from an opportunity
958
+ const content = await stack0.marketing.createContent({
959
+ projectSlug: 'my-project',
960
+ environment: 'production',
961
+ contentType: 'tiktok_slideshow',
962
+ title: 'How AI is Changing Marketing',
963
+ opportunityId: 'opp-id',
964
+ });
965
+
966
+ // Review workflow
967
+ await stack0.marketing.approveContent({ contentId: content.id, reviewNotes: 'Looks great!' });
968
+ // or
969
+ await stack0.marketing.rejectContent({ contentId: content.id, reviewNotes: 'Needs revisions' });
970
+
971
+ // Create and version scripts
972
+ const script = await stack0.marketing.createScript({
973
+ projectSlug: 'my-project',
974
+ environment: 'production',
975
+ hook: 'Are you ready to see the future?',
976
+ slides: [{ order: 0, text: 'AI is changing everything', voiceoverText: '...', duration: 3 }],
977
+ cta: 'Follow for more!',
978
+ });
979
+ ```
980
+
981
+ ### Content Calendar
982
+
983
+ ```typescript
984
+ const entry = await stack0.marketing.scheduleContent({
985
+ projectSlug: 'my-project',
986
+ contentId: 'content-id',
987
+ scheduledFor: new Date('2024-12-25T10:00:00Z'),
988
+ autoPublish: true,
989
+ });
990
+
991
+ const entries = await stack0.marketing.listCalendarEntries({
992
+ projectSlug: 'my-project',
993
+ startDate: new Date('2024-12-01'),
994
+ endDate: new Date('2024-12-31'),
995
+ });
996
+ ```
997
+
998
+ ### Analytics and Usage
999
+
1000
+ ```typescript
1001
+ const overview = await stack0.marketing.getAnalyticsOverview({
1002
+ projectSlug: 'my-project',
1003
+ environment: 'production',
1004
+ });
1005
+
1006
+ const performance = await stack0.marketing.getContentPerformance({
1007
+ projectSlug: 'my-project',
1008
+ environment: 'production',
1009
+ contentType: 'tiktok_slideshow',
1010
+ });
1011
+
1012
+ const usage = await stack0.marketing.getCurrentUsage({
1013
+ projectSlug: 'my-project',
1014
+ environment: 'production',
1015
+ });
1016
+ ```
1017
+
1018
+ ---
1019
+
1020
+ ## Workflows
1021
+
1022
+ Create and execute AI-powered workflows with step-by-step orchestration.
1023
+
1024
+ ### Creating Workflows
1025
+
1026
+ ```typescript
1027
+ const workflow = await stack0.workflows.create({
1028
+ slug: 'content-pipeline',
1029
+ name: 'Content Generation Pipeline',
1030
+ steps: [
1031
+ {
1032
+ id: 'generate',
1033
+ name: 'Generate Content',
1034
+ type: 'llm',
1035
+ provider: 'anthropic',
1036
+ model: 'claude-sonnet-4-20250514',
1037
+ config: {
1038
+ prompt: 'Write a blog post about {{topic}}',
1039
+ maxTokens: 2000,
1040
+ },
1041
+ },
1042
+ ],
1043
+ variables: [
1044
+ { name: 'topic', type: 'string', required: true },
1045
+ ],
1046
+ });
1047
+ ```
1048
+
1049
+ ### Running Workflows
1050
+
1051
+ ```typescript
1052
+ // Run and wait for the result (simplest approach)
1053
+ const run = await stack0.workflows.runAndWait({
1054
+ workflowSlug: 'content-pipeline',
1055
+ variables: { topic: 'artificial intelligence' },
1056
+ }, { timeout: 120000 }); // 2 minute timeout
1057
+
1058
+ console.log(run.output);
1059
+
1060
+ // Async run with webhook notification
1061
+ const { id } = await stack0.workflows.run({
1062
+ workflowSlug: 'content-pipeline',
1063
+ variables: { topic: 'machine learning' },
1064
+ webhook: {
1065
+ url: 'https://your-app.com/webhook',
1066
+ secret: 'webhook-secret',
1067
+ },
1068
+ });
1069
+
1070
+ // Check run status
1071
+ const status = await stack0.workflows.getRun({ id });
1072
+ console.log(status.status); // 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
1073
+ ```
1074
+
1075
+ ### Managing Workflows
1076
+
1077
+ ```typescript
1078
+ const { items } = await stack0.workflows.list({ environment: 'production', isActive: true });
1079
+ await stack0.workflows.update({ id: 'workflow-id', name: 'Updated Pipeline', isActive: false });
1080
+ await stack0.workflows.delete({ id: 'workflow-id' });
1081
+ await stack0.workflows.cancelRun({ id: 'run-id' });
1082
+ ```
1083
+
1084
+ ### Workflows Method Reference
1085
+
1086
+ | Method | Description |
1087
+ |--------|-------------|
1088
+ | `create(req)` | Create a new workflow |
1089
+ | `get(req)` | Get workflow by ID or slug |
1090
+ | `list(req?)` | List workflows |
1091
+ | `update(req)` | Update a workflow |
1092
+ | `delete(req)` | Delete a workflow |
1093
+ | `run(req)` | Start a workflow run |
1094
+ | `getRun(req)` | Get run status and output |
1095
+ | `listRuns(req?)` | List workflow runs |
1096
+ | `cancelRun(req)` | Cancel a running workflow |
1097
+ | `runAndWait(req, opts?)` | Run and poll until complete |
1098
+
1099
+ ---
1100
+
1101
+ ## Error Handling
1102
+
1103
+ All SDK methods throw errors with additional properties for API errors:
1104
+
1105
+ ```typescript
1106
+ import { Stack0 } from '@stack0/sdk';
1107
+ import type { Stack0Error } from '@stack0/sdk';
1108
+
1109
+ try {
1110
+ await stack0.mail.send({
1111
+ from: 'hello@yourapp.com',
1112
+ to: 'user@example.com',
1113
+ subject: 'Test',
1114
+ html: '<p>Hello</p>',
1115
+ });
1116
+ } catch (err) {
1117
+ const error = err as Stack0Error;
1118
+
1119
+ if (error.statusCode === 401) {
1120
+ console.error('Invalid API key');
1121
+ } else if (error.statusCode === 403) {
1122
+ console.error('Insufficient permissions');
1123
+ } else if (error.statusCode === 400) {
1124
+ console.error('Validation error:', error.message);
1125
+ } else if (error.statusCode === 429) {
1126
+ console.error('Rate limit exceeded');
1127
+ } else {
1128
+ console.error('Unexpected error:', error.message);
1129
+ }
1130
+
1131
+ // Full error response from the API
1132
+ console.error(error.response);
1133
+ console.error(error.code);
1134
+ }
1135
+ ```
1136
+
1137
+ The `Stack0Error` interface extends `Error` with:
1138
+
1139
+ | Property | Type | Description |
1140
+ |----------|------|-------------|
1141
+ | `statusCode` | `number` | HTTP status code |
1142
+ | `code` | `string` | Machine-readable error code |
1143
+ | `response` | `unknown` | Full error response body from the API |
1144
+
1145
+ ---
1146
+
1147
+ ## TypeScript Types
1148
+
1149
+ The SDK exports all request/response types for each module. Import them directly:
1150
+
1151
+ ```typescript
1152
+ import type {
1153
+ // Shared
1154
+ Environment,
1155
+ Stack0Error,
1156
+ BatchJobStatus,
1157
+ ScheduleFrequency,
1158
+ PaginatedRequest,
1159
+ PaginatedResponse,
1160
+
1161
+ // Mail
1162
+ SendEmailRequest,
1163
+ SendEmailResponse,
1164
+ EmailStatus,
1165
+ ListEmailsRequest,
1166
+ Template,
1167
+ Campaign,
1168
+ Sequence,
1169
+ MailContact,
1170
+
1171
+ // CDN
1172
+ Asset,
1173
+ TransformOptions,
1174
+ UploadUrlRequest,
1175
+ ListAssetsRequest,
1176
+ TranscodeVideoRequest,
1177
+ PrivateFile,
1178
+
1179
+ // Screenshots
1180
+ Screenshot,
1181
+ CreateScreenshotRequest,
1182
+
1183
+ // Extraction
1184
+ ExtractionResult,
1185
+ CreateExtractionRequest,
1186
+
1187
+ // Integrations
1188
+ Connection,
1189
+ Contact,
1190
+ Company,
1191
+ Deal,
1192
+
1193
+ // Marketing
1194
+ Trend,
1195
+ Opportunity,
1196
+ Content,
1197
+ Script,
1198
+ CalendarEntry,
1199
+
1200
+ // Workflows
1201
+ Workflow,
1202
+ WorkflowRun,
1203
+ CreateWorkflowRequest,
1204
+ RunWorkflowRequest,
1205
+ } from '@stack0/sdk';
1206
+ ```
1207
+
1208
+ You can also import types from individual module entry points:
1209
+
1210
+ ```typescript
1211
+ import type { SendEmailRequest } from '@stack0/sdk/mail';
1212
+ import type { Asset, TransformOptions } from '@stack0/sdk/cdn';
1213
+ import type { Screenshot } from '@stack0/sdk/screenshots';
1214
+ import type { ExtractionResult } from '@stack0/sdk/extraction';
1215
+ ```
1216
+
1217
+ ---
1218
+
1219
+ ## Polling Helpers
1220
+
1221
+ Several methods include built-in polling for async operations:
1222
+
1223
+ | Method | Default Poll Interval | Default Timeout |
1224
+ |--------|----------------------|-----------------|
1225
+ | `screenshots.captureAndWait()` | 1s | 60s |
1226
+ | `screenshots.batchAndWait()` | 2s | 5min |
1227
+ | `extraction.extractAndWait()` | 1s | 60s |
1228
+ | `extraction.batchAndWait()` | 2s | 5min |
1229
+ | `workflows.runAndWait()` | 2s | 10min |
1230
+
1231
+ Override defaults via the options parameter:
1232
+
1233
+ ```typescript
1234
+ const result = await stack0.screenshots.captureAndWait(
1235
+ { url: 'https://example.com', format: 'png' },
1236
+ { pollInterval: 500, timeout: 30000 }, // 500ms interval, 30s timeout
1237
+ );
1238
+ ```
1239
+
1240
+ ---
1241
+
1242
+ ## Migration from Resend
1243
+
1244
+ The Stack0 Mail API is designed for compatibility with the Resend API:
1245
+
1246
+ ```typescript
1247
+ // Before (Resend)
1248
+ import { Resend } from 'resend';
1249
+ const resend = new Resend('re_...');
1250
+ await resend.emails.send({ from: '...', to: '...', subject: '...', html: '...' });
1251
+
1252
+ // After (Stack0)
1253
+ import { Stack0 } from '@stack0/sdk';
1254
+ const stack0 = new Stack0({ apiKey: 'stack0_...' });
1255
+ await stack0.mail.send({ from: '...', to: '...', subject: '...', html: '...' });
1256
+ ```
1257
+
1258
+ ---
602
1259
 
603
- ## Support
1260
+ ## Links
604
1261
 
605
- - Documentation: https://docs.stack0.com
606
- - Issues: https://github.com/CampbellVentures/stack0/issues
607
- - Email: support@stack0.com
1262
+ - [Documentation](https://stack0.dev/docs)
1263
+ - [GitHub](https://github.com/stack0dev/sdk)
1264
+ - [Changelog](https://github.com/stack0dev/sdk/releases)
608
1265
 
609
1266
  ## License
610
1267
 
611
- MIT
1268
+ [MIT](https://opensource.org/licenses/MIT)