Post with media — end-to-end
A copy-paste-ready flow for posting an image or video. Covers every step from verifying your API key to recovering a failed platform publish.
This guide uses accountIds to target accounts — the platform is inferred from each account. platforms is only used in validate, not in create.
Verify auth and find your account
TypeScript
import { VoxBurstClient } from '@voxburst/sdk'
const client = new VoxBurstClient({ apiKey: process.env.VOXBURST_API_KEY! })
const { data: accounts } = await client.accounts.list({ status: 'ACTIVE' })
const instagram = accounts.find(a => a.platform === 'instagram')
if (!instagram) throw new Error('No active Instagram account connected')
console.log(`Posting to: ${instagram.displayName} (${instagram.id})`)Upload the file
Request a presigned URL, then PUT the file bytes directly to S3.
TypeScript
import { readFileSync } from 'fs'
// Step 1 — request presigned URL
const uploadRes = await fetch('https://api.voxburst.io/v1/media/upload', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VOXBURST_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
filename: 'launch-photo.jpg',
contentType: 'image/jpeg',
sizeBytes: 512_000,
}),
})
const { mediaId, uploadUrl, uploadHeaders } = await uploadRes.json()
// Step 2 — PUT bytes to S3 (no auth header — S3 handles auth via the signed URL)
const file = readFileSync('./launch-photo.jpg')
await fetch(uploadUrl, {
method: 'PUT',
headers: uploadHeaders,
body: file,
})
console.log(`Uploaded: ${mediaId}`)Poll until READY
Media goes through PENDING → PROCESSING → READY (or FAILED). Poll every 2–3 seconds.
TypeScript
async function waitForMedia(mediaId: string, timeoutMs = 60_000) {
const deadline = Date.now() + timeoutMs
while (Date.now() < deadline) {
const res = await fetch(`https://api.voxburst.io/v1/media/${mediaId}`, {
headers: { Authorization: `Bearer ${process.env.VOXBURST_API_KEY}` },
})
const media = await res.json()
if (media.status === 'READY') return media
if (media.status === 'FAILED') {
throw new Error(`Media processing failed: ${JSON.stringify(media.validationResults)}`)
}
await new Promise(r => setTimeout(r, 2500))
}
throw new Error(`Media ${mediaId} did not become READY within ${timeoutMs}ms`)
}
const media = await waitForMedia(mediaId)
console.log(`Ready: ${media.publicUrl}`)Validate content against the target platform
TypeScript
const validation = await client.posts.validate({
content: 'Launch day 🚀 #product #buildinpublic',
platforms: ['INSTAGRAM'], // UPPERCASE platform constants for validate
})
if (!validation.valid) {
for (const [platform, result] of Object.entries(validation.platforms)) {
if (!result.valid) console.error(`${platform}: ${result.errors.join(', ')}`)
}
throw new Error('Validation failed — fix content before posting')
}Create the post with an idempotency key
The idempotency key makes this step safe to retry — a duplicate key + body returns the cached response without creating a second post.
TypeScript
import crypto from 'crypto'
const content = 'Launch day 🚀 #product #buildinpublic'
const scheduledFor = '2026-07-01T15:00:00Z'
// Deterministic key: safe to re-run on failure
const idempotencyKey = crypto
.createHash('sha256')
.update(`${instagram.id}:${content}:${scheduledFor}`)
.digest('hex')
.slice(0, 64)
const post = await client.posts.create({
content,
accountIds: [instagram.id], // platform inferred from account
contentType: 'IMAGE',
media: [mediaId],
scheduledFor,
firstComment: '#photography #voxburst',
})
// With idempotency key (raw fetch — SDK exposes this via options in v1.3+)
// Alternatively pass { headers: { 'Idempotency-Key': idempotencyKey } } in SDK v1.3+
console.log(`Post created: ${post.id} (status: ${post.status})`)Track publish status
Poll
async function waitForPublish(postId: string, timeoutMs = 300_000) {
const deadline = Date.now() + timeoutMs
while (Date.now() < deadline) {
const post = await client.posts.get(postId)
if (['published', 'partial', 'failed'].includes(post.status)) return post
await new Promise(r => setTimeout(r, 5000))
}
throw new Error(`Post ${postId} did not finish within ${timeoutMs}ms`)
}
const result = await waitForPublish(post.id)
for (const p of result.platforms) {
const icon = p.status === 'published' ? '✅' : '❌'
console.log(`${icon} ${p.platform}: ${p.platformPostUrl ?? p.error?.message}`)
}Fix a failed platform
If one platform failed (status partial), fix just that destination without re-posting to the platforms that succeeded.
const post = await client.posts.get(postId)
const failed = post.platforms.filter(p => p.status === 'failed')
for (const platform of failed) {
console.log(`Fixing ${platform.platform}: ${platform.error?.message}`)
// fixPlatform is REST-only — use raw fetch
await fetch(`https://api.voxburst.io/v1/posts/${post.id}/platforms/${platform.postPlatformId}/fix`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.VOXBURST_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ /* content: 'Adjusted caption', mediaIds: [...] */ }),
})
}
// The post will re-enter the publish queue for the fixed platforms onlyRelated
- Media API reference — upload, validation results, video requirements
- Posts API reference — full field reference and content overrides
- Recovery playbook — diagnosing every category of publish failure
- Idempotency — key format rules and retry behavior
Last updated on