Schedule and publish
Create scheduled posts, publish immediately, and post X/Twitter threads through the DunSocial API, using bearer auth and your workspace id header.
Post routes need Bearer auth and X-Workspace-Id.
Schedule a post
POST /api/posts/schedule
curl -X POST https://api.dunsocial.com/api/posts/schedule \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"socialAccountIds": ["ACCOUNT_ID"],
"content": "Shipping something small today.",
"scheduledAt": "2026-08-01T15:00:00.000Z",
"mediaUrls": []
}'Returns 201 with one post per social account. Validation is all-or-nothing: a character-limit or platform-metadata error creates zero posts. After validation passes, each account is inserted and queued. If QStash fails mid-batch, earlier accounts in that groupId may already be scheduled.
Validate without writing
POST /api/posts/validate
Same body as schedule / publish-now, without scheduledAt. Runs the same media, character-limit, per-platform metadata, and X monthly-cap checks. Does not create posts or queue jobs.
curl -X POST https://api.dunsocial.com/api/posts/validate \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Workspace-Id: YOUR_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"socialAccountIds": ["ACCOUNT_ID"],
"content": "Shipping something small today.",
"mediaUrls": []
}'Returns 200:
{
"success": true,
"data": {
"valid": false,
"issues": [
{
"socialAccountId": "ACCOUNT_ID",
"platform": "X",
"code": "content_length",
"message": "Content exceeds X character limit: 283/280 characters"
}
]
}
}valid: true and an empty issues array means the same payload can be sent to schedule or publish-now.
Publish now
POST /api/posts/publish-now
Same body as schedule, without scheduledAt. Optional naturalPosting boolean.
Threads (X only)
| Method | Path |
|---|---|
POST |
/api/posts/schedule-thread |
POST |
/api/posts/publish-thread-now |
Body shape:
{
"socialAccountId": "ACCOUNT_ID",
"scheduledAt": "2026-08-01T15:00:00.000Z",
"tweets": [
{ "content": "First tweet", "mediaUrls": [] },
{ "content": "Second tweet" }
]
}Threads need 2–25 tweets. Response includes threadHeadPost, totalTweets, and postIds.
Required per-platform metadata
When socialAccountIds includes that platform, the matching metadata block is required. There is no default Instagram format (do not assume reel).
| Platform | Required field | Required keys | Notes |
|---|---|---|---|
metadata.reddit |
subreddit, title, kind |
kind: self, link, image, or video. Flair when the subreddit requires it. |
|
metadata.instagram |
format |
format: feed_image, carousel, reel, or story. Optional caption (max 2200, at most 5 hashtags). Feed/carousel/story images: JPEG, PNG, GIF, or WebP (non-JPEG converts at publish). |
|
metadata.pinterest |
boardId, title, description |
Exactly one image. | |
| TikTok | metadata.tiktok |
privacyLevel |
Exactly one video. Optional title (max 2200). |
| Discord | metadata.discord |
channelId |
Text or announcement channel from the connected server. Optional channelName. Up to 10 attachments; total request max 25 MB. |
| Slack | metadata.slack |
channelId |
Public or private channel from the connected Slack team. Optional channelName. Up to 10 attachments; 50 MB per file. |
X, LinkedIn, Bluesky, Threads, YouTube, and Facebook Pages do not need a metadata block for a text/media post. Facebook Pages are text, one link, and photos only (no video).
Character limits (on content)
| Platform | Limit |
|---|---|
| X | 280, or 25,000 when the connected account is X Premium |
| Bluesky | 300 |
| Threads | 500 |
| 3,000 | |
2,200 (caption in metadata.instagram is also capped at 2,200) |
|
| TikTok | 2,200 |
| Facebook Pages | 63,206 (images only; no metadata.facebook) |
800 (metadata.pinterest.description also 800; title 100) |
|
| YouTube | 5,000 description (title 100, handled separately) |
40,000 (metadata.reddit.title max 300) |
|
| Discord | 2,000 |
| Slack | 4,000 |
Media on a post
mediaUrls can be media asset ids, https://media.dunsocial.com/... URLs, or storage keys like media/uploads/.... Stored and returned posts keep asset ids in mediaUrls.
Common errors
| Status | Cause |
|---|---|
| 400 | Character limits, missing/invalid platform options, bad media |
| 404 | Media asset missing |
| 429 | X monthly posting cap |
Check cap usage: GET /api/posts/x-cap-usage. Dry-run the same checks with POST /api/posts/validate.