POST/v1/social-posts

Publish content to connected social platforms. Supports immediate publish, scheduled posts, drafts, per-platform captions, media attachments, threads, and first comments.

Requires AuthIdempotentRate Limited

Overview

The social posts endpoint is the core of UniPost. It accepts a caption and a list of account IDs, and publishes the content to every connected platform in a single API call.

For multi-platform fan-out where each platform should get different copy, use platform_posts[] instead of caption + account_ids. Each entry becomes one platform post with its own caption, media, and options.

Posts that fail on some platforms but succeed on others return status: "partial" with per-platform results — the successful publishes are never rolled back.

Authentication

All requests require a Bearer token in the Authorization header.

Authorization: Bearer up_live_xxxx

Get your API key at app.unipost.dev

Request

Base URL: https://api.unipost.dev
Headers
ParameterTypeRequiredDescription
AuthorizationstringBearer {api_key}
Content-Typestringapplication/json
Idempotency-KeystringXAlternative to the body field. Max 64 characters, 24h TTL.
Body Parameters
ParameterTypeRequiredDescription
captionstringXText content. Max length varies by platform. Required unless platform_posts is set.
account_idsstring[]XSocial account IDs to fan out to. Use this OR platform_posts, not both.
platform_postsobject[]XPer-platform posts with individual captions, media, and options. Preferred for multi-platform fan-out.
media_urlsstring[]XPublic URLs of images/videos to attach. Ignored when platform_posts is set.
media_idsstring[]XIDs from POST /v1/media (preferred over media_urls). Resolved server-side to presigned download URLs.
scheduled_atstringXISO 8601 timestamp. If set, post is queued and published by the scheduler at that time. Must be at least 60 seconds in the future.
idempotency_keystringXUnique string (max 64 chars). Same key + same workspace within 24h returns the original response unchanged.
statusstringXSet to "draft" to persist without publishing. Use POST /v1/social-posts/:id/publish to ship later.
platform_posts[] object
ParameterTypeRequiredDescription
account_idstringTarget social account ID.
captionstringXPlatform-specific caption. Overrides the top-level caption.
media_urlsstring[]XPlatform-specific media URLs.
media_idsstring[]XPlatform-specific media IDs from the media library.
thread_positionintegerX1-indexed position in a multi-post thread. All entries with the same account_id and non-zero thread_position form one thread. Twitter + Bluesky supported.
first_commentstringXText posted as the first reply/comment after the main post lands. Supported on Twitter, LinkedIn, Instagram. Bluesky/Threads reject this — use thread_position instead.
platform_optionsobjectXPlatform-specific key-value options (e.g. Twitter poll, LinkedIn visibility).
Two request shapes — pass exactly one: caption + account_ids (same caption everywhere) or platform_posts[] (different caption per platform). Mixing both is rejected with VALIDATION_ERROR.

Examples

Basic — same caption to multiple platforms

const response = await fetch(
  'https://api.unipost.dev/v1/social-posts',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer up_live_xxxx',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      caption: 'Hello from UniPost! \u{1F680}',
      account_ids: ['sa_instagram_123', 'sa_linkedin_456'],
    }),
  }
);

const { data } = await response.json();
console.log(data.id);      // post_abc123
console.log(data.status);  // "published"

Advanced — per-platform captions + scheduling + idempotency

// Per-platform captions + scheduling + idempotency
const response = await fetch(
  'https://api.unipost.dev/v1/social-posts',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer up_live_xxxx',
      'Content-Type': 'application/json',
      'Idempotency-Key': 'release-v1.4.0-2026-04-08',
    },
    body: JSON.stringify({
      scheduled_at: '2026-05-01T09:00:00Z',
      platform_posts: [
        {
          account_id: 'sa_twitter_789',
          caption: 'v1.4 is live \u{1F389} webhooks + bulk publish',
        },
        {
          account_id: 'sa_linkedin_456',
          caption: 'We just shipped v1.4 with two features our customers have been asking for: webhook event subscriptions and a bulk publish endpoint that accepts up to 50 posts in one call.',
        },
        {
          account_id: 'sa_bluesky_012',
          caption: 'v1.4 shipped \u{2014} webhooks and bulk publish are live',
        },
      ],
    }),
  }
);

Response

200 — Success
{
  "data": {
    "id": "post_abc123",
    "caption": "Hello from UniPost!",
    "status": "published",
    "scheduled_at": null,
    "created_at": "2026-04-08T10:00:00Z",
    "results": [
      {
        "social_account_id": "sa_instagram_123",
        "platform": "instagram",
        "status": "published",
        "external_id": "17841234567890",
        "published_at": "2026-04-08T10:00:01Z",
        "error_message": null
      },
      {
        "social_account_id": "sa_linkedin_456",
        "platform": "linkedin",
        "status": "published",
        "external_id": "urn:li:share:7049876543210",
        "published_at": "2026-04-08T10:00:02Z",
        "error_message": null
      }
    ]
  }
}

Status values: published (all platforms succeeded), scheduled (queued for future), partial (some succeeded, some failed), failed (all platforms failed), draft (saved, not published).

422 — Validation Error
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Caption exceeds maximum length for twitter (280 characters)",
    "issues": [
      {
        "field": "platform_posts[0].caption",
        "code": "caption_too_long",
        "message": "Caption is 312 characters, max is 280 for twitter"
      }
    ]
  }
}

Error Codes

CodeHTTPDescription
VALIDATION_ERROR422Request body failed validation (caption too long, missing required fields, invalid media, etc.). Check error.issues[] for per-field details.
VALIDATION_ERROR400Structural validation error (malformed JSON, invalid field types).
UNAUTHORIZED401Missing or invalid API key.
NOT_FOUND404Account ID not found in this workspace.
CONFLICT409Idempotency key already used with different request body.
INTERNAL_ERROR500Server error. Retry with the same idempotency key.

Platform-specific errors (e.g. Twitter rate limit, Instagram media rejection) are returned in results[].error_message per-platform, not as top-level errors.

Limits & Constraints

Max account_ids per request20
Max platform_posts per request20
Rate limit100 requests/min per API key
Idempotency key TTL24 hours
Max media per postVaries by platform (1-10)

Platform caption limits

PlatformMax Characters
X / Twitter280
LinkedIn3,000
Instagram2,200
Threads500
TikTok2,200
YouTube5,000
Bluesky300 (graphemes)

Changelog

v1.4(April 2026)
  • Added first_comment field on platform_posts[]
  • Added media_ids (replaces media_urls for managed uploads)
  • Added per-account monthly quota enforcement
v1.3(March 2026)
  • Added thread_position for Twitter + Bluesky threads
  • Added bulk endpoint (POST /v1/social-posts/bulk)
v1.2(February 2026)
  • Added platform_posts[] for per-platform overrides
  • Added scheduled_at for deferred publishing
  • Added idempotency_key support
v1.0(January 2026)
  • Initial release — caption + account_ids shape
← View full docs|Last updated: April 2026 · API v1