Skip to content

The uploads resource is the heart of the SDK — everything that creates, publishes, or inspects content.

Methods ​

MethodWhat 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:

typescript
// 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 ID

Posting media ​

medias accepts any mix of:

  • Buffer / Blob — raw file, auto-uploaded to your media library first
  • string media library ID (24-char hex, e.g. 6a04f715d3d7d93575e65d13)
  • string public URL (https://...)
typescript
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 ​

typescript
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:

typescript
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 entirely

Checking status ​

getStatus accepts either the content ID or a requestId — whichever your ack gave you:

typescript
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:

typescript
{
  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):

typescript
const final = await socifyr.uploads.waitForCompletion(sync.requestId!, 3000, 300_000)
console.log(final.status) // 'completed'

Drafts ​

typescript
// 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 ​

typescript
const ack = await socifyr.uploads.retry('6b1f0c2a...')
console.log(ack.retriedChannels) // 2 — failed channels re-queued

Successful 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):

typescript
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:

typescript
const detail = await socifyr.uploads.get('6b1f0c2a...')
console.log(detail.status, detail.channels.length, detail.scheduledAt)

Archive / delete ​

typescript
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):

typescript
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:

typescript
import type {
  Upload,
  TierUsage,
  UploadVideoParams,
  UploadPhotosParams,
  UploadTextParams,
  UploadDocumentParams,
  UploadDetail,
  UploadSummary,
  UploadListResponse,
  UploadStatusResponse,
  UploadStatusChannel,
  UpdateDraftParams,
  UploadHistoryQuery,
} from '@socifyr/sdk'