Skip to Content
SdksTypeScript SDK

TypeScript SDK

The @voxburst/sdk package is the official TypeScript/JavaScript SDK for the VoxBurst API. It provides full TypeScript support, automatic retries, cursor-based auto-pagination, and typed error classes.

npm install @voxburst/sdk

What do you want to do?

Jump straight to the task:


Build your first app

A guided path from installation to a published post.

import { VoxBurstClient } from '@voxburst/sdk' const client = new VoxBurstClient({ apiKey: process.env.VOXBURST_API_KEY! }) const ctx = { signal: AbortSignal.timeout(30_000) } // 1. Verify auth works const { data: accounts } = await client.accounts.list() console.log(`Connected accounts: ${accounts.length}`) // [{ id: 'acc_twitter_abc', platform: 'twitter', displayName: 'My Brand' }, ...] // 2. Find the account you want to post to const instagram = accounts.find(a => a.platform === 'instagram') if (!instagram) throw new Error('No Instagram account connected') // 3. Validate your content before creating the post const validation = await client.posts.validate({ content: 'Behind the scenes of our launch day 📸', platforms: ['INSTAGRAM'], }) if (!validation.valid) { console.error('Validation failed:', validation.platforms) process.exit(1) } // 4. Create a draft first const draft = await client.posts.create({ content: 'Behind the scenes of our launch day 📸', accountIds: [instagram.id], contentType: 'IMAGE', media: ['media_abc1234567890abc12345678'], // VoxBurst media ID saveAsDraft: true, }) console.log(`Draft created: ${draft.id} (status: ${draft.status})`) // { id: 'post_abc123', status: 'draft', ... } // 5. Schedule it const scheduled = await client.posts.update(draft.id, { scheduledFor: '2026-06-01T15:00:00Z', saveAsDraft: false, }) console.log(`Scheduled for: ${scheduled.scheduledFor}`) // 6. Inspect results after it publishes const result = await client.posts.get(draft.id) console.log(`Status: ${result.status}`) for (const p of result.platforms ?? []) { console.log(` ${p.platform}: ${p.status} — ${p.platformPostUrl ?? 'pending'}`) } // 7. Handle an error cleanly try { await client.posts.get('post_doesnotexist') } catch (err) { if (err instanceof NotFoundError) console.log('Not found') else if (err instanceof RateLimitError) console.log(`Rate limited — retry after ${err.retryAfter}s`) else throw err }

Configuration

const client = new VoxBurstClient({ apiKey: process.env.VOXBURST_API_KEY!, // required baseUrl: 'https://api.voxburst.io', // optional; defaults to production timeout: 30_000, // optional; ms maxRetries: 3, // optional; default 3 })
OptionTypeRequiredDescription
apiKeystringYesVoxBurst API key (vb_live_…)
baseUrlstringNoAPI base URL override
timeoutnumberNoRequest timeout in milliseconds
maxRetriesnumberNoNumber of retries on retriable errors (default: 3)

The non-null assertion (!) is required in strict TypeScript. Store VOXBURST_API_KEY in your environment — never hardcode it.


Runtime compatibility

EnvironmentSupportNotes
Node.js✅ 18+Requires native fetch (built-in since Node 18). Polyfill with node-fetch on Node 16.
BunNative fetch built-in — no configuration needed.
DenoImport via npm specifier: npm:@voxburst/sdk.
Browser⚠️ PartialWorks in modern browsers but API keys are exposed client-side — use a server proxy in production.
Cloudflare WorkersNative fetch available.
Vercel / Next.js EdgeTested on Vercel Edge Runtime and Next.js middleware.

Module format: ships both ESM (import) and CJS (require). Tree-shaking works with ESM bundlers. No native addons.

Custom fetch: if your runtime lacks a global fetch, pass fetchImplementation:

import fetch from 'node-fetch' const client = new VoxBurstClient({ apiKey: process.env.VOXBURST_API_KEY!, fetchImplementation: fetch as any, // Node 16 polyfill })

Which media path should I use?

My media is…What to doSDK method
At a public HTTPS URLRegister via REST, get a media IDPOST /v1/media/register (REST)
A local file on my serverRequest presigned URL, PUT bytes, poll until READYPOST /v1/media/upload (REST) → PUT → poll
Already in VoxBurstUse the mediaId you already havePass directly as media: ['media_abc...']
Needs platform validationUpload then call validateUpload → POST /v1/media/:id/validate (REST)
// Register a public URL (no upload needed) const res = await fetch('https://api.voxburst.io/v1/media/register', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.VOXBURST_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://cdn.example.com/photo.jpg', contentType: 'image/jpeg' }), }) const { id: mediaId } = await res.json() // Use in a post const post = await client.posts.create({ content: 'Check this out!', accountIds: ['acc_instagram_abc'], contentType: 'IMAGE', media: [mediaId], })

Complete Instagram example

Instagram requires contentType and at least one media item on every post.

// Step 1: find the Instagram account const { data: accounts } = await client.accounts.list() const ig = accounts.find(a => a.platform === 'instagram')! // Step 2: validate const v = await client.posts.validate({ content: 'Launch day. Shipping now. 🚀', platforms: ['INSTAGRAM'], }) // { valid: true, platforms: { INSTAGRAM: { valid: true, errors: [], warnings: [] } } } // Step 3: post const post = await client.posts.create({ content: 'Launch day. Shipping now. 🚀', accountIds: [ig.id], contentType: 'IMAGE', // required for Instagram media: ['media_abc1234567890abc12345678'], // pre-uploaded media ID scheduledFor: '2026-06-01T15:00:00Z', firstComment: '#launch #newproduct #buildinpublic', firstCommentDelay: 60, // seconds after publish }) console.log(post.id, post.status) // post_abc123 scheduled // Step 4: check result after it publishes const result = await client.posts.get(post.id) console.log(result.platforms?.[0]?.platformPostUrl) // https://www.instagram.com/p/ABC123/
const carousel = await client.posts.create({ content: 'Swipe to see all the new features →', accountIds: [ig.id], contentType: 'CAROUSEL', media: ['media_slide1', 'media_slide2', 'media_slide3'], })

Reel

const reel = await client.posts.create({ content: 'Our latest drop 🔥', accountIds: [ig.id], contentType: 'REEL', media: ['media_video_abc123'], })

Cross-platform example

Post the same content to multiple platforms with per-platform overrides.

const { data: accounts } = await client.accounts.list() const accountIds = accounts .filter(a => ['instagram', 'twitter', 'linkedin'].includes(a.platform)) .map(a => a.id) // Validate on all target platforms first const v = await client.posts.validate({ content: 'Announcing VoxBurst 2.0 — unified social scheduling.', platforms: ['INSTAGRAM', 'TWITTER', 'LINKEDIN'], }) if (!v.valid) throw new Error(JSON.stringify(v.platforms)) const post = await client.posts.create({ content: 'Announcing VoxBurst 2.0 — unified social scheduling.', accountIds, contentType: 'IMAGE', media: ['media_launch_image'], platformOverrides: { TWITTER: { content: 'VoxBurst 2.0 just shipped 🚀 #ProductLaunch #SocialMedia' }, LINKEDIN: { content: 'Today we are shipping VoxBurst 2.0. After 12 months of building, it is finally ready for teams that publish at scale across every channel.' }, }, firstComment: '#VoxBurst20 #Launch', firstCommentDelay: 60, }) // After publishing const result = await client.posts.get(post.id) // result.status: 'published' | 'partial' | 'failed' for (const p of result.platforms ?? []) { console.log(`${p.platform}: ${p.status} — ${p.platformPostUrl}`) } // Retry any failures if (result.status === 'partial' || result.status === 'failed') { await client.posts.retry(post.id) }

Post status lifecycle

Understanding which methods are valid from each status:

StatusupdateretrydeleteMeaning
draftSaved but not queued
scheduledQueued for future publish
publishingIn flight — do not touch
publishedAll platforms succeeded
failedAll platforms failed
partialSome platforms failed
cancelledManually cancelled
const post = await client.posts.get('post_abc123') if (post.status === 'partial' || post.status === 'failed') { const retried = await client.posts.retry(post.id) console.log(`Retried: ${retried.retriedPlatforms?.length} platforms re-queued`) } if (post.status === 'failed') { // Check per-platform errors for (const p of post.platforms ?? []) { if (p.status === 'failed') console.log(`${p.platform}: ${p.error}`) } }

Validate before posting

const result = await client.posts.validate({ content: 'My post content here', platforms: ['INSTAGRAM', 'TWITTER', 'LINKEDIN'], }) // { // valid: false, // platforms: { // INSTAGRAM: { valid: true, errors: [], warnings: [{ code: 'NO_MEDIA', message: '...' }] }, // TWITTER: { valid: false, errors: [{ code: 'CONTENT_TOO_LONG', message: '...' }], warnings: [] }, // LINKEDIN: { valid: true, errors: [], warnings: [] } // } // } if (!result.valid) { for (const [platform, r] of Object.entries(result.platforms)) { if (!r.valid) console.error(`${platform}: ${r.errors.map(e => e.message).join(', ')}`) } }

Get post results

const post = await client.posts.get('post_abc123') // { // id: 'post_abc123', // content: '...', // status: 'published', // publishedAt: '2026-06-01T15:00:05Z', // platforms: [ // { platform: 'instagram', status: 'published', platformPostUrl: 'https://www.instagram.com/p/...' }, // { platform: 'twitter', status: 'failed', error: 'CONTENT_TOO_LONG', retryCount: 1 } // ] // }

Schedule a post

const post = await client.posts.create({ content: 'Launch day 🚀', accountIds: ['acc_instagram_abc'], contentType: 'IMAGE', media: ['media_launch'], scheduledFor: '2026-06-01T15:00:00Z', }) // { id: 'post_abc123', status: 'scheduled', scheduledFor: '2026-06-01T15:00:00Z' }

client.posts reference

// Create → POST /v1/posts → Post await client.posts.create({ content, // string — required accountIds, // string[] — required; platform inferred from each account contentType?, // 'TEXT' | 'IMAGE' | 'VIDEO' | 'CAROUSEL' | 'STORY' | 'REEL' | 'THREAD' media?, // string[] — VoxBurst media IDs scheduledFor?, // ISO 8601 UTC string saveAsDraft?, // boolean firstComment?, // string firstCommentDelay?,// number (0–60 seconds) platformOverrides?,// Record<string, { content?: string; media?: string[] }> queue?, // boolean — mutually exclusive with scheduledFor tags?, // string[] metadata?, // Record<string, unknown> personaId?, // string threadPosts?, // ThreadItem[] isManualSave?, // boolean }) // Read → GET /v1/posts/:id → Post await client.posts.get(postId) // List → GET /v1/posts → { data: Post[], pagination: Pagination } await client.posts.list({ status?, platform?, from?, to?, limit?, cursor? }) // Auto-paginate all pages → AsyncIterable<Post> for await (const post of client.posts.listAll({ status: 'published' })) { console.log(post.id) } // Update → PATCH /v1/posts/:id → Post await client.posts.update(postId, { content?, scheduledFor?, accountIds?, media?, platformOverrides?, firstComment?, firstCommentDelay?, saveAsDraft?, tags?, metadata?, personaId?, }) // Lifecycle operations await client.posts.cancel(postId) // → POST /v1/posts/:id/cancel — cancel scheduled post; returns Post await client.posts.retry(postId) // → POST /v1/posts/:id/retry — retry failed/partial; returns Post await client.posts.unpublish(postId) // → POST /v1/posts/:id/unpublish — remove from platforms await client.posts.delete(postId) // → DELETE /v1/posts/:id — draft/scheduled/failed only // Note: publish(id), clone(id), fixPlatform(id, pid) are REST-only — not in the TypeScript SDK. // For fixPlatform: POST /v1/posts/:id/platforms/:pid/fix // Validate → POST /v1/posts/validate → ValidationResult const v = await client.posts.validate({ content: '...', platforms: ['INSTAGRAM', 'TWITTER'], // UPPERCASE platform constants }) // v.valid: boolean, v.platforms: Record<string, { valid: boolean, errors: string[] }>

client.accounts reference

// List → GET /v1/accounts → { data: Account[] } const { data } = await client.accounts.list({ status?: 'ACTIVE' | 'EXPIRED' | 'DISCONNECTED' }) // [{ id: 'acc_instagram_abc', platform: 'instagram', displayName: 'My Brand', username: 'mybrand' }] // Get → GET /v1/accounts/:id → Account const account = await client.accounts.get('acc_abc123') // Connect (OAuth) → POST /v1/accounts/connect/:platform → { url: string } const { url } = await client.accounts.initiateConnect('TWITTER', 'https://yourapp.com/oauth/callback') // Redirect the user to `url` to complete OAuth // Maintenance await client.accounts.refresh('acc_abc123') // → POST /v1/accounts/:id/refresh — refresh OAuth tokens await client.accounts.delete('acc_abc123') // → DELETE /v1/accounts/:id — disconnect account // Note: completeConnect() and test() are REST-only — use raw fetch for the OAuth callback step.

client.media reference

Media upload is a multi-step presign flow. The TypeScript SDK wraps the presign and polling steps:

// Step 1 — request an upload URL → POST /v1/media/upload → { uploadUrl, mediaId } // (TypeScript SDK: use raw fetch for the S3 PUT; Go SDK has client.Media.RequestUploadURL) const res = await fetch('https://api.voxburst.io/v1/media/upload', { method: 'POST', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: 'photo.jpg', contentType: 'image/jpeg', sizeBytes: 204800 }), }) const { uploadUrl, mediaId } = await res.json() // Step 2 — upload to S3 directly (no auth header) await fetch(uploadUrl, { method: 'PUT', body: fileBlob, headers: { 'Content-Type': 'image/jpeg' } }) // Step 3 — poll until READY (status transitions: PENDING → PROCESSING → READY or FAILED) let media do { await new Promise(r => setTimeout(r, 2000)) const r = await fetch(`https://api.voxburst.io/v1/media/${mediaId}`, { headers: { Authorization: `Bearer ${apiKey}` } }) media = await r.json() } while (media.status === 'PENDING' || media.status === 'PROCESSING') if (media.status !== 'READY') throw new Error(`Media failed: ${JSON.stringify(media.validationResults)}`) // Use the mediaId in a post await client.posts.create({ content: '...', accountIds: ['acc_123'], media: [mediaId] })

See Media API reference for the full upload flow, validation results, and video requirements.


client.webhooks reference

// List → GET /v1/webhooks → { data: WebhookEndpoint[] } const { data } = await client.webhooks.list() // Create → POST /v1/webhooks → WebhookEndpoint (secret returned only here) const endpoint = await client.webhooks.create({ url: 'https://yourapp.com/webhooks/voxburst', events: ['post.published', 'post.failed'], // or ['*'] for all events }) console.log(endpoint.secret) // save this — never returned again // Get → GET /v1/webhooks/:id → WebhookEndpoint await client.webhooks.get('wh_abc123') // Update → PATCH /v1/webhooks/:id → WebhookEndpoint await client.webhooks.update('wh_abc123', { events: ['post.published'], enabled: true }) // Delete → DELETE /v1/webhooks/:id → void await client.webhooks.delete('wh_abc123') // Note: webhooks.test() is REST-only — POST /v1/webhooks/test with raw fetch.

See Webhooks API reference for payload shapes, signature verification, and retry behavior.


Analytics (REST-only)

@voxburst/sdk does not wrap analytics endpoints — use raw fetch with your API key. All 21 analytics routes follow the same pattern. See the Analytics API reference for the full endpoint list, query parameters, and response shapes.

const BASE = 'https://api.voxburst.io' const headers = { Authorization: `Bearer ${process.env.VOXBURST_API_KEY}` } // Per-post metrics const postMetrics = await fetch( `${BASE}/v1/analytics/posts/post_abc123?startDate=2026-05-01&endDate=2026-05-31`, { headers } ).then(r => r.json()) // { data: [{ date: '2026-05-01', impressions: 1200, likes: 45, ... }] } // Account-level metrics const acctMetrics = await fetch( `${BASE}/v1/analytics/accounts/acc_abc123?startDate=2026-05-01&endDate=2026-05-31`, { headers } ).then(r => r.json()) // { followers: 12500, avgEngagementRate: 0.034, totalPosts: 42 } // AI Insights (paid plans — requires system AI provider config) const insights = await fetch(`${BASE}/v1/analytics/insights`, { headers }).then(r => r.json()) // { insights: [{ type, category, priority, text, detail, action, actionRoute }], ... } // Hashtag performance const hashtags = await fetch( `${BASE}/v1/analytics/hashtag-performance?startDate=2026-05-01&endDate=2026-05-31&limit=10`, { headers } ).then(r => r.json()) // { hashtags: [{ tag, postCount, avgEngagementRate, totalImpressions, totalEngagements, topPlatforms }], ... }

Pagination and batch guidance

// list() — one page at a time; use when you need control over pagination const page1 = await client.posts.list({ status: 'published', limit: 20 }) if (page1.pagination.has_more) { const page2 = await client.posts.list({ cursor: page1.pagination.next_cursor }) } // listAll() — iterates all pages; use for data exports or processing everything let count = 0 for await (const post of client.posts.listAll({ status: 'published' })) { count++ // Each post is typed; safe to use immediately } console.log(`Total published posts: ${count}`) // batch.createPosts() — create up to 50 posts in one request const { results } = await client.batch.createPosts([ { content: 'Post one', accountIds: ['acc_123'] }, { content: 'Post two', accountIds: ['acc_456'], scheduledFor: '2026-06-01T10:00:00Z' }, ]) // results: [{ id: 'post_abc1', status: 'created' }, { id: 'post_abc2', status: 'created' }]

When to use what:

  • list() — when you need one page or want pagination control
  • listAll() — when you need every result (export, sync, reporting)
  • batch.createPosts() — when creating many posts at once (content calendars, bulk import)

Production integration patterns

// Store the API key in environment — never hardcode const client = new VoxBurstClient({ apiKey: process.env.VOXBURST_API_KEY! }) // Use timeout and maxRetries appropriate for your use case const client = new VoxBurstClient({ apiKey: process.env.VOXBURST_API_KEY!, timeout: 30_000, // 30s — increase for video upload operations maxRetries: 3, // SDK retries automatically on 429 and 5xx }) // Always validate before posting in production async function safePost(content: string, accountIds: string[], platforms: string[]) { const v = await client.posts.validate({ content, platforms }) if (!v.valid) throw new Error(`Validation failed: ${JSON.stringify(v.platforms)}`) return client.posts.create({ content, accountIds }) } // Handle rate limits explicitly import { RateLimitError } from '@voxburst/sdk' async function postWithBackoff(params: CreatePostInput, maxAttempts = 3) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await client.posts.create(params) } catch (err) { if (err instanceof RateLimitError && attempt < maxAttempts) { await new Promise(r => setTimeout(r, (err.retryAfter ?? 60) * 1000)) continue } throw err } } }

Error handling

import { VoxBurstError, ValidationError, AuthenticationError, NotFoundError, RateLimitError, ConflictError, } from '@voxburst/sdk' try { await client.posts.get('post_notfound') } catch (err) { if (err instanceof NotFoundError) { console.log('Post not found') } else if (err instanceof RateLimitError) { console.log(`Rate limited — retry after ${err.retryAfter}s`) } else if (err instanceof ValidationError) { console.log('Validation failed:', err.errors) } else if (err instanceof AuthenticationError) { console.log('Invalid API key') } else if (err instanceof ConflictError) { console.log('Optimistic concurrency conflict — reload and retry') } else if (err instanceof VoxBurstError) { console.log(`API error ${err.status}: ${err.code} — ${err.message}`) } }
ClassHTTP statusWhen thrown
AuthenticationError401Missing or invalid API key
ValidationError400/422Invalid request; includes errors map
NotFoundError404Resource not found
RateLimitError429Rate limit exceeded; includes retryAfter seconds
ConflictError409Optimistic concurrency conflict on update
VoxBurstErrorall othersBase class for all API errors

Other resources

All resources follow the standard { data: T[], pagination: Pagination } envelope for list methods and return T directly for single-resource methods. The SDK does not rename response keys — client.contacts.list() returns { data: Contact[], pagination }, not { contacts }.

See the SDK ↔ REST mapping for the complete method-to-endpoint table for every resource.

// Batch const { results } = await client.batch.execute([...]) const { results: posts } = await client.batch.createPosts([...]) // Contacts (CRM) const { data: contacts } = await client.contacts.list({ search: 'alice', platform: 'TWITTER' }) const contact = await client.contacts.create({ platform: 'TWITTER', platformId: '...', displayName: 'Alice' }) // Broadcasts (Agency plan) const broadcast = await client.broadcasts.create({ name: '...', messageTemplate: '...', platforms: ['TWITTER'] }) // Sequences (Agency plan) const sequence = await client.sequences.create({ name: 'Onboarding Flow', exitOnReply: true }) // Note: Personas, Hashtag Sets, and Approval Workflows are REST-only — not in the TypeScript SDK. // GET/POST /v1/personas — see /api-reference/personas // GET/POST /v1/hashtag-sets — see /api-reference/hashtag-sets // GET/POST /v1/approval-workflows — see /api-reference/approval-workflows

Next steps

Last updated on