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/sdkWhat do you want to do?
Jump straight to the task:
- Post text to one platform
- Post an image to Instagram
- Schedule a post
- Post to multiple platforms at once
- Validate before posting
- Fetch post results
- Create many posts at once
- Recover from a rate limit
- Upload a local file
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! })
// 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/v1', // optional; defaults to production
timeout: 30_000, // optional; ms
maxRetries: 3, // optional; default 3
})| Option | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | VoxBurst API key (vb_live_…) |
baseUrl | string | No | API base URL override. Must begin with https:// — see below |
timeout | number | No | Request timeout in milliseconds |
maxRetries | number | No | Number 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.
baseUrl must use HTTPS. As of @voxburst/sdk v1.3.0 the constructor throws immediately if baseUrl is set to anything not beginning with https://, so your API key cannot be sent over plaintext. There is no exemption for localhost. If you point the SDK at a local or proxied API, terminate TLS in front of it. Leaving baseUrl unset is unaffected — the default is already HTTPS.
Runtime compatibility
| Environment | Support | Notes |
|---|---|---|
| Node.js | ✅ 18+ | Requires native fetch (built-in since Node 18). Polyfill with node-fetch on Node 16. |
| Bun | ✅ | Native fetch built-in — no configuration needed. |
| Deno | ✅ | Import via npm specifier: npm:@voxburst/sdk. |
| Browser | ⚠️ Partial | Works in modern browsers but API keys are exposed client-side — use a server proxy in production. |
| Cloudflare Workers | ✅ | Native fetch available. |
| Vercel / Next.js Edge | ✅ | Tested 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.
Which media path should I use?
| My media is… | What to do | SDK method |
|---|---|---|
| At a public HTTPS URL | Register via REST, get a media ID | POST /v1/media/register (REST) |
| A local file on my server | Request presigned URL, PUT bytes, poll until READY | POST /v1/media/upload (REST) → PUT → poll |
| Already in VoxBurst | Use the mediaId you already have | Pass directly as media: ['media_abc...'] |
| Needs platform validation | Upload then call validate | Upload → 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/Carousel
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:
| Status | update | retry | delete | Meaning |
|---|---|---|---|---|
draft | ✅ | ✗ | ✅ | Saved but not queued |
scheduled | ✅ | ✗ | ✅ | Queued for future publish |
publishing | ✗ | ✗ | ✗ | In flight — do not touch |
published | ✗ | ✗ | ✗ | All platforms succeeded |
failed | ✅ | ✅ | ✅ | All platforms failed |
partial | ✅ | ✅ | ✗ | Some platforms failed |
cancelled | ✗ | ✗ | ✅ | Manually 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(`Retry queued — new status: ${retried.status}`)
}
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[] }
// list() only forwards `limit`/`cursor` to the API — there is no server-side `status` filter.
const { data } = await client.accounts.list({ limit: 50 })
// [{ id: 'acc_instagram_abc', platform: 'INSTAGRAM', displayName: 'My Brand', username: 'mybrand', status: 'ACTIVE' }]
// Filter by status client-side instead
const activeAccounts = data.filter(a => a.status === 'ACTIVE')
// 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
// Select the Page / organization to publish as → POST /v1/accounts/:id/select-page
await client.accounts.selectPage('acc_abc123', 'page_456', { pageName: 'Acme Co' })
// Pass an empty pageId to clear the selection and publish as the personal profile (LinkedIn)
await client.accounts.selectPage('acc_abc123', '')
// 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.selectPage() requires the accounts:write scope and throws ValidationError (HTTP 400) with a code you should branch on:
code | Meaning |
|---|---|
PAGE_SELECTION_NOT_SUPPORTED | Account was connected via Instagram Direct Login, which has no Facebook Pages. Reconnect via Facebook Login for Business first. |
ACCOUNT_PAGE_MISMATCH | That Page belongs to a different Instagram account. Each Instagram account is a separate connected account — select the Page from the matching account’s own record. |
DESTINATIONS_NOT_LOADED | Destinations are still being fetched — retry. |
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 }
const { uploadUrl, mediaId } = await client.media.getUploadUrl({
filename: 'photo.jpg',
contentType: 'image/jpeg',
sizeBytes: 204800,
})
// Step 2 — upload to S3 directly. This is a raw fetch in every SDK: the presigned
// URL must be sent without your API key, or S3 rejects the signature.
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))
media = await client.media.get(mediaId)
} while (media.status === 'PENDING' || media.status === 'PROCESSING')
if (media.status !== 'READY') throw new Error(`Media failed to process: ${mediaId}`)
// Delete when no longer needed → DELETE /v1/media/:id
await client.media.delete(mediaId)
// 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.
client.analytics reference
The SDK wraps four analytics routes. The remaining routes are REST-only — see Analytics (REST-only) below.
// List post metrics across the workspace → GET /v1/analytics/posts
// Response shape: { posts, has_more, next_cursor } — not { data, pagination }
const { posts, has_more, next_cursor } = await client.analytics.listPostAnalytics({
startDate: '2026-05-01',
endDate: '2026-05-31',
platforms: ['INSTAGRAM', 'LINKEDIN'],
limit: 50,
})
// posts: PostAnalytics[] — each with { postId, platform, metrics: { impressions, reach, engagements, likes, ... }, dataAvailable, granularity }
// Metrics for one post → GET /v1/analytics/posts/:id
const { data: postMetrics } = await client.analytics.getPostAnalytics('post_abc123', {
startDate: '2026-05-01',
endDate: '2026-05-31',
granularity: 'DAILY', // 'HOURLY' | 'DAILY' | 'WEEKLY' | 'MONTHLY'
})
// Account-level metrics → GET /v1/analytics/accounts/:id
const acct = await client.analytics.getAccountAnalytics('acc_abc123', {
startDate: '2026-05-01',
endDate: '2026-05-31',
})
// acct.metrics: { followers, following, totalImpressions, totalReach, totalEngagements, totalPosts, engagementRate }
// Workspace overview with period-over-period deltas → GET /v1/analytics/overview
const overview = await client.analytics.getOverview({ startDate: '2026-05-01', endDate: '2026-05-31' })
// overview.summary, overview.comparison.deltas, overview.topPostsEvery metrics item carries a dataAvailable boolean. Check it before reading metrics — a platform that has not yet reported returns zeroed metrics rather than an error, so zeros are not necessarily real zeros.
Analytics (REST-only)
The remaining analytics routes are not wrapped by the SDK — use raw fetch with your API key. 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}` }
// 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) {
// next_cursor is typed `string | null`; list() expects `cursor?: string` — coerce null to undefined
const page2 = await client.posts.list({ cursor: page1.pagination.next_cursor ?? undefined })
}
// 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 controllistAll()— 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}`)
}
}| Class | HTTP status | When thrown |
|---|---|---|
AuthenticationError | 401 | Missing or invalid API key |
ValidationError | 400/422 | Invalid request; includes errors map |
NotFoundError | 404 | Resource not found |
RateLimitError | 429 | Rate limit exceeded; includes retryAfter seconds |
ConflictError | 409 | Optimistic concurrency conflict on update |
VoxBurstError | all others | Base class for all API errors |
Other resources
Most list methods follow the standard { data: T[], pagination: Pagination } envelope and return T directly for single-resource methods. client.contacts.list() is an exception — it returns { contacts: Contact[], total, page, limit, pages }, not { data, pagination }. Check each resource’s actual return type rather than assuming the standard envelope.
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)
// Note the response shape: { contacts, total, page, limit, pages } — not { data, pagination }
const { contacts } = await client.contacts.list({ search: 'alice', platform: 'TWITTER' })
const contact = await client.contacts.create({ platform: 'TWITTER', platformId: '...', displayName: 'Alice' })
await client.contacts.addTag('con_abc123', 'vip')
await client.contacts.removeTag('con_abc123', 'vip')
// contacts.get() returns ContactDetail — Contact plus recent engagement history
const { contact: detail } = await client.contacts.get('con_abc123')
detail.comments // ContactComment[] — comments they left on your posts
detail.inboundMessages // ContactInboundMessage[] — DMs they sent you
// Broadcasts (Agency plan)
const broadcast = await client.broadcasts.create({ name: '...', messageTemplate: '...', platforms: ['TWITTER'] })
const { deliveries } = await client.broadcasts.listDeliveries('bc_abc123', { page: 1, limit: 50 })
// Sequences (Agency plan)
const sequence = await client.sequences.create({ name: 'Onboarding Flow', exitOnReply: true })
const { steps } = await client.sequences.listSteps('seq_abc123')
await client.sequences.enroll('seq_abc123', 'con_abc123', 'TWITTER')
// exitEnrollment takes the ENROLLMENT id, not the contact id
await client.sequences.exitEnrollment('seq_abc123', 'enroll_xyz789')
// 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-workflowsContactDetail.comments and ContactDetail.inboundMessages are truncated by the API and carry no cursor, so they are a recent-activity preview rather than a complete record. Do not treat an empty array as proof the contact has never commented or messaged you — page the inbox endpoints if you need the full history. The response also carries deliveries and sequenceEnrollments, which are not yet typed in the SDK.
Next steps
- SDK ↔ REST mapping — complete method-to-endpoint table for every resource
- Getting Started — first API calls and environment setup
- API Reference — full REST endpoint documentation
- Python (REST) — no official Python SDK; REST-only with full examples
- MCP Server — let AI assistants manage your workspace
- Platform guides — platform-specific posting examples
- Common tasks — task-oriented recipes