The uploads resource is the heart of the SDK — everything that creates, publishes, or inspects content.
Methods
| Method | What it does |
|---|---|
video(params) | Publish a video |
photos(params) | Publish 1–10 photos |
text(params) | Publish a text post |
document(params) | Publish a document (LinkedIn) |
getStatus(idOrRequestId) | Current status + per-channel results |
waitForCompletion(id, interval?, timeout?) | Poll until terminal |
list(query?) | Paginated history |
get(id) | Full detail for one post |
update(id, params) | Edit a draft |
cancel(id) | Delete a post / cancel a schedule |
publishNow(id) | Publish a scheduled post immediately |
publishDraft(id) | Publish a saved draft |
retry(id) | Re-queue failed channels |
archive(id) / unarchive(id) | Hide / restore in history |
bulkDelete(ids) / bulkArchive(ids) / bulkUnarchive(ids) | Bulk lifecycle |
The ack: sync, async, draft
Every create/publish method returns the same normalized Upload object. The API returns uploadId (immediate & scheduled) or draftId (drafts); the SDK normalizes both onto id. On the async: true path there is no ID at all — only a requestId to poll:
// 1. Immediate (default): id + requestId + tier usage
const sync = await socifyr.uploads.text({
connections: ['x-myhandle'],
text: 'Hello world',
})
console.log(sync.id) // 6b1f0c2a9d3e4f5a6b7c8d9e
console.log(sync.requestId) // 9f2c7a1e-3d44-4b2a-8e1f-0a5c2d7e9f10
console.log(sync.usage) // { count: 4, limit: 100, remaining: 96 }
// 2. Async: requestId ONLY — poll with it
const started = await socifyr.uploads.video({
connections: ['youtube-mybrand'],
medias: [bigVideoBuffer],
async: true,
})
console.log(started.id) // undefined
console.log(started.requestId) // use this for getStatus
console.log(started.totalChannels) // 1
// 3. Draft: the draft ID
const draft = await socifyr.uploads.text({
connections: ['x-myhandle'],
text: 'Saved for later',
saveDraft: true,
})
console.log(draft.id) // the draft IDPosting media
medias accepts any mix of:
Buffer/Blob— raw file, auto-uploaded to your media library firststringmedia library ID (24-char hex, e.g.6a04f715d3d7d93575e65d13)stringpublic URL (https://...)
import { readFileSync } from 'fs'
await socifyr.uploads.video({
connections: ['instagram-mybrand', 'tiktok-me', 'youtube-mybrand'],
medias: [readFileSync('demo.mp4')],
title: 'Behind the scenes',
contentFormat: 'short_video', // hint: Reel / Short / Story
platformOptions: {
'instagram-mybrand': { mediaType: 'REELS' },
'tiktok-me': { privacyLevel: 'PUBLIC_TO_EVERYONE' },
},
})
await socifyr.uploads.photos({
connections: ['instagram-mybrand'],
text: 'Carousel of the week',
medias: [
readFileSync('a.jpg'),
readFileSync('b.jpg'),
'https://cdn.example.com/c.png',
'6a04f715d3d7d93575e65d13',
],
contentFormat: 'photo_carousel',
firstComment: '#bts #studio',
})
await socifyr.uploads.document({
connections: ['linkedin-mybrand'],
medias: [readFileSync('whitepaper.pdf')],
title: 'The 2026 Social Report',
})Only one video per post.
Posting text
await socifyr.uploads.text({
connections: ['x-myhandle', 'linkedin-myhandle', 'threads-mybrand'],
text: 'Just shipped v2.0 🚀',
firstComment: { text: '#devtools #shipping', linkUrl: 'https://socifyr.com' },
platformOptions: {
'x-myhandle': { replySettings: 'EVERYONE' },
'linkedin-myhandle': { visibility: 'PUBLIC' },
},
})Scheduling
Any create method accepts scheduledAt (ISO-8601). timezone pins a wall-clock time to a zone:
const scheduled = await socifyr.uploads.text({
connections: ['linkedin-myhandle'],
text: 'Tomorrow at 9am',
scheduledAt: '2026-06-01T09:00:00Z',
timezone: 'America/New_York',
})
console.log(scheduled.scheduledAt) // echoed back
// Change of plans:
await socifyr.uploads.publishNow(scheduled.id!) // publish right now
await socifyr.uploads.cancel(scheduled.id!) // or cancel entirelyChecking status
getStatus accepts either the content ID or a requestId — whichever your ack gave you:
const status = await socifyr.uploads.getStatus(sync.requestId!)
console.log(status.status) // 'publishing' | 'completed' | 'partial_failure' | ...
for (const ch of status.channels) {
console.log(ch.platform, ch.result?.status, ch.result?.postUrl, ch.result?.error)
}Full channel shape:
{
requestId: '9f2c7a1e-...',
uploadId: '6b1f0c2a...',
status: 'partial_failure',
type: 'video',
channels: [
{
platform: 'youtube',
connectionId: '6a20...',
targetName: 'My Brand Channel',
result: {
status: 'success',
postId: '6zaztTmk3Ck',
postUrl: 'https://youtube.com/shorts/6zaztTmk3Ck',
publishedAt: '2026-09-12T10:04:11.000Z',
},
},
{
platform: 'instagram',
connectionId: '6a21...',
result: { status: 'failed', error: 'Requires instagram_content_publish permission' },
},
],
createdAt: '2026-09-12T10:03:58.000Z',
}Polling helper
waitForCompletion(id, intervalMs?, timeoutMs?) polls every 3s until status is completed, failed, or partial_failure, or the timeout hits (default 5 min):
const final = await socifyr.uploads.waitForCompletion(sync.requestId!, 3000, 300_000)
console.log(final.status) // 'completed'Drafts
// Save
const draft = await socifyr.uploads.photos({
connections: ['instagram-mybrand'],
medias: [readFileSync('banner.jpg')],
text: 'Polish me later',
saveDraft: true,
})
// Edit — returns a { id: draftId } ack, not the full detail.
// Omitted fields stay unchanged.
await socifyr.uploads.update(draft.id!, {
text: 'Final caption',
connections: ['instagram-mybrand', 'facebook-mybrand'],
})
// Publish — async ack: poll with requestId or the draft ID
const ack = await socifyr.uploads.publishDraft(draft.id!)
console.log(ack.requestId, ack.totalChannels)See the Drafts guide for the full workflow.
Retrying
const ack = await socifyr.uploads.retry('6b1f0c2a...')
console.log(ack.retriedChannels) // 2 — failed channels re-queuedSuccessful channels are skipped via an idempotency guard — you can't double-post.
Listing history
Items live under data, pagination under meta (note: totalPages, not pages):
const history = await socifyr.uploads.list({
page: 1,
limit: 20,
status: 'completed',
platform: 'instagram',
type: 'video',
from: '2026-01-01',
to: '2026-06-01',
})
console.log(history.meta.total, history.meta.totalPages)
for (const item of history.data) {
console.log(item.id, item.status, item.mediaThumbnail?.url)
}get(id) returns the full detail for one post:
const detail = await socifyr.uploads.get('6b1f0c2a...')
console.log(detail.status, detail.channels.length, detail.scheduledAt)Archive / delete
await socifyr.uploads.archive(id) // hide from history without deleting
await socifyr.uploads.unarchive(id)
await socifyr.uploads.cancel(id) // hard delete
await socifyr.uploads.bulkDelete([id1, id2, id3])
await socifyr.uploads.bulkArchive([id1, id2, id3])
await socifyr.uploads.bulkUnarchive([id1, id2, id3])Per-platform options
platformOptions keys can be connection slugs, connection ObjectIds, or platform names (for a platform-wide override):
await socifyr.uploads.video({
connections: ['instagram-mybrand', 'tiktok-me', 'facebook-mybrand', 'linkedin-myhandle', 'x-myhandle'],
medias: [videoBuffer],
title: 'Default title',
platformOptions: {
'instagram-mybrand': {
mediaType: 'REELS', // 'REELS' | 'STORIES' | 'FEED'
title: 'Instagram-specific title',
coverUrl: 'https://example.com/cover.jpg',
},
'tiktok-me': {
privacyLevel: 'PUBLIC_TO_EVERYONE', // | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY'
disableDuet: false,
disableComment: false,
disableStitch: false,
},
'facebook-mybrand': {
mediaType: 'REELS', // 'FEED_VIDEO' | 'STORIES' | 'REELS'
pageId: '1004860882719628',
},
'linkedin-myhandle': {
visibility: 'PUBLIC', // 'PUBLIC' | 'CONNECTIONS'
},
'x-myhandle': {
replySettings: 'EVERYONE', // 'EVERYONE' | 'FOLLOWERS' | 'MENTIONED_USERS'
text: 'Shorter text for X',
},
},
})Every platform also accepts title, description, text, firstComment, and linkUrl overrides.
Full type reference
The TypeScript types are exported from the SDK root:
import type {
Upload,
TierUsage,
UploadVideoParams,
UploadPhotosParams,
UploadTextParams,
UploadDocumentParams,
UploadDetail,
UploadSummary,
UploadListResponse,
UploadStatusResponse,
UploadStatusChannel,
UpdateDraftParams,
UploadHistoryQuery,
} from '@socifyr/sdk'