> ## Documentation Index
> Fetch the complete documentation index at: https://tryunsora.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Post

> Schedule or save Instagram and TikTok reels and slideshows.

Schedule a post for later or save it as a **draft**. Publishing runs through Unsora when the post is published from the scheduler.

## Supported content

| `media.type` | Use for                               | Response `type` |
| ------------ | ------------------------------------- | --------------- |
| `video`      | Instagram Reel, TikTok Reel           | `video`         |
| `slideshow`  | Instagram carousel, TikTok photo post | `slideshow`     |

Connect **Instagram** and **TikTok** via [Get accounts](/api-reference/accounts/get_accounts).

## Before you post

1. Active **paid plan** + API key or Clerk JWT.
2. [Get accounts](/api-reference/accounts/get_accounts) → copy each account `id`.
3. Pick `video` or `slideshow`. For Instagram reels, optional `cover_url`.

## Request body

<ParamField body="caption" type="string" required>
  Post caption. Legacy alias: `mainCaption`.
</ParamField>

<ParamField body="accounts" type="array" required>
  `[{ "id": "<account-uuid>" }]` — up to 10 accounts.
</ParamField>

<ParamField body="media" type="object | null">
  <Expandable title="Video (reel)">
    <ParamField body="type" type="string" required>`video`</ParamField>
    <ParamField body="url" type="string" required>MP4/MOV URL reachable by Unsora.</ParamField>
    <ParamField body="duration_sec" type="number">Video length in seconds.</ParamField>

    <ParamField body="cover_url" type="string">
      Optional **Instagram Reel** cover image (JPG/PNG URL). Ignored by TikTok.
    </ParamField>
  </Expandable>

  <Expandable title="Slideshow">
    <ParamField body="type" type="string" required>`slideshow`</ParamField>
    <ParamField body="urls" type="string[]" required>1–35 image URLs in order.</ParamField>
  </Expandable>
</ParamField>

<ParamField body="settings" type="object">
  Optional publish options for **Instagram and TikTok only** (not Facebook, YouTube, etc.). You can also send `instagram` or `tiktok` at the **top level** of the body (same shape as below). Other keys under `settings` return `400`.

  <Expandable title="Instagram — settings.instagram">
    <ParamField body="cover_url" type="string">
      Custom **Reel** cover image URL (JPG/PNG). Same as `media.cover_url`.
    </ParamField>
  </Expandable>

  <Expandable title="TikTok — settings.tiktok">
    <ParamField body="post_mode" type="string">
      `DIRECT_POST` (default) or `MEDIA_UPLOAD` (inbox draft — user finishes in TikTok).
    </ParamField>

    <ParamField body="privacy_level" type="string">
      e.g. `PUBLIC_TO_EVERYONE`, `SELF_ONLY`. Must match [creator info](https://developers.tiktok.com/doc/content-posting-api-reference-query-creator-info) for the account.
    </ParamField>

    <ParamField body="music_usage_confirmation" type="boolean">
      Confirm music usage when TikTok requires it.
    </ParamField>

    <ParamField body="disable_comment" type="boolean" />

    <ParamField body="disable_duet" type="boolean" />

    <ParamField body="disable_stitch" type="boolean" />

    <ParamField body="brand_content_toggle" type="boolean">Paid partnership (third-party brand).</ParamField>
    <ParamField body="brand_organic_toggle" type="boolean">Promoting creator's own business.</ParamField>
    <ParamField body="is_aigc" type="boolean">AI-generated content flag.</ParamField>
    <ParamField body="auto_add_music" type="boolean">Slideshow only — TikTok adds background music.</ParamField>
    <ParamField body="photo_cover_index" type="integer">Slideshow only — cover image index (0-based).</ParamField>
    <ParamField body="video_cover_timestamp_ms" type="integer">Reel only — cover frame time in ms.</ParamField>
  </Expandable>
</ParamField>

<ParamField body="scheduled_at" type="string">
  ISO 8601 with timezone, **≥ 2 minutes** ahead. Omit for **draft**.
</ParamField>

<ParamField body="external_id" type="string">
  Idempotency key (max 128 chars) or use `Idempotency-Key` header.
</ParamField>

## Instagram reel cover

Only **`cover_url`** — a public image URL:

```json theme={null}
"media": {
  "type": "video",
  "url": "https://cdn.example.com/reel.mp4",
  "cover_url": "https://cdn.example.com/cover.jpg"
}
```

TikTok reel cover frame: optional `settings.tiktok.video_cover_timestamp_ms`.

## Platform settings (optional)

Supported providers: **Instagram**, **TikTok** — only `settings.instagram` and `settings.tiktok` (or top-level `instagram` / `tiktok`). Nothing is required; omit `settings` if you do not need them.

<Tip>
  Top-level `tiktok` / `instagram` keys work the same as `settings.tiktok` / `settings.instagram`.
</Tip>

```json theme={null}
"settings": {
  "instagram": { "cover_url": "https://cdn.example.com/cover.jpg" },
  "tiktok": {
    "post_mode": "MEDIA_UPLOAD",
    "privacy_level": "SELF_ONLY",
    "music_usage_confirmation": true
  }
}
```

## Response

```json theme={null}
{
  "success": true,
  "message": "Post saved as draft",
  "data": {
    "id": "cm123abc",
    "status": "draft",
    "type": "video",
    "caption": "Behind the scenes",
    "scheduled_at": null,
    "accounts": ["acc_instagram"]
  }
}
```

## Examples

<RequestExample>
  ```bash Instagram reel with cover theme={null} theme={null}
  curl -X POST "https://mvp2.tryunsora.com/api/v1/posts" \
    -H "Authorization: Bearer uns_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "caption": "Behind the scenes",
      "media": {
        "type": "video",
        "url": "https://cdn.example.com/reel.mp4",
        "cover_url": "https://cdn.example.com/cover.jpg"
      },
      "accounts": [{ "id": "YOUR_INSTAGRAM_ACCOUNT_ID" }]
    }'
  ```

  ```bash TikTok reel theme={null} theme={null}
  curl -X POST "https://mvp2.tryunsora.com/api/v1/posts" \
    -H "Authorization: Bearer uns_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "caption": "Quick tip",
      "media": {
        "type": "video",
        "url": "https://cdn.example.com/tip.mp4"
      },
      "accounts": [{ "id": "YOUR_TIKTOK_ACCOUNT_ID" }]
    }'
  ```

  ```bash Slideshow theme={null} theme={null}
  curl -X POST "https://mvp2.tryunsora.com/api/v1/posts" \
    -H "Authorization: Bearer uns_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "caption": "3 looks for the weekend",
      "media": {
        "type": "slideshow",
        "urls": [
          "https://cdn.example.com/1.jpg",
          "https://cdn.example.com/2.jpg"
        ]
      },
      "accounts": [{ "id": "YOUR_INSTAGRAM_ACCOUNT_ID" }]
    }'
  ```

  ```bash TikTok slideshow + settings theme={null} theme={null}
  curl -X POST "https://mvp2.tryunsora.com/api/v1/posts" \
    -H "Authorization: Bearer uns_live_YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "caption": "3 outfits for the long weekend",
      "media": {
        "type": "slideshow",
        "urls": [
          "https://cdn.example.com/look-1.jpg",
          "https://cdn.example.com/look-2.jpg",
          "https://cdn.example.com/look-3.jpg"
        ]
      },
      "accounts": [{ "id": "YOUR_TIKTOK_ACCOUNT_ID" }],
      "tiktok": {
        "post_mode": "MEDIA_UPLOAD",
        "music_usage_confirmation": true,
        "privacy_level": "SELF_ONLY"
      },
      "external_id": "tiktok-draft-001"
    }'
  ```
</RequestExample>

## Errors

| HTTP  | When                            |
| ----- | ------------------------------- |
| `400` | Validation failed               |
| `401` | Invalid auth                    |
| `403` | No paid plan or unknown account |
| `404` | User not found                  |
| `409` | Idempotency conflict            |
| `500` | Server error                    |


## OpenAPI

````yaml POST /posts
openapi: 3.1.0
info:
  title: Unsora API
  version: 1.2.0
  description: >-
    Public API for Unsora integrations. Authenticate with Clerk JWT or
    uns_live_* API key. API keys are rate-limited to 60 requests/minute and
    support idempotency keys. Async jobs complete via polling GET
    /image/status/{id}.
servers:
  - url: https://mvp.tryunsora.com/api/v1
    description: Versioned API (recommended)
security: []
tags:
  - name: Clippings
  - name: Connect
  - name: Image Generations
  - name: Music Generations
  - name: Influencer Studio
  - name: Posts
  - name: Thumbnails
  - name: User
  - name: Video Generations
paths:
  /posts:
    post:
      tags:
        - Posts
      summary: Create Post
      description: >-
        Create a draft or scheduled post for one or more connected accounts.
        Requires an active paid plan.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            maxLength: 128
          description: >-
            Optional. Replays the original response for 24h when using an API
            key. Same as body external_id.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePostRequest'
      responses:
        '200':
          description: Idempotent replay of an earlier create
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatePostResponse'
        '201':
          description: Post created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatePostResponse'
        '400':
          description: Validation error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Paid plan required or account not owned
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PlanRequiredError'
                  - $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: User not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - bearerAuth: []
components:
  schemas:
    CreatePostRequest:
      type: object
      properties:
        caption:
          type: string
          description: Post caption shown on connected platforms
        mainCaption:
          type: string
          description: Legacy alias for caption
        accounts:
          type: array
          items:
            $ref: '#/components/schemas/PostAccountRef'
          minItems: 1
          maxItems: 10
        media:
          anyOf:
            - $ref: '#/components/schemas/PostMediaVideo'
            - $ref: '#/components/schemas/PostMediaSlideshow'
            - type: 'null'
          description: >-
            video, slideshow, or null for text-only drafts. Legacy: array of
            {type, url} items.
        scheduled_at:
          type: string
          format: date-time
          description: ISO 8601 with timezone. At least 2 minutes ahead. Omit for draft.
        scheduledFor:
          type: string
          format: date-time
          description: Legacy alias for scheduled_at
        timezone:
          type: string
          description: IANA timezone label stored with the post
        external_id:
          type: string
          maxLength: 128
          description: >-
            Idempotency key (alias for Idempotency-Key header on API key
            requests)
        settings:
          $ref: '#/components/schemas/PostSettings'
          description: >-
            Optional Instagram and TikTok publish options only. Same fields may
            be sent at the top level as `instagram` or `tiktok`.
        instagram:
          $ref: '#/components/schemas/PostSettingsInstagram'
          description: Optional alias for settings.instagram
        tiktok:
          $ref: '#/components/schemas/PostSettingsTiktok'
          description: Optional alias for settings.tiktok
      required:
        - accounts
    CreatePostResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          $ref: '#/components/schemas/PublicPostSummary'
        message:
          type: string
      required:
        - success
        - data
        - message
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
        error:
          type: string
        code:
          type: string
        message:
          type: string
      required:
        - error
    PlanRequiredError:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          type: string
        code:
          type: string
          enum:
            - PLAN_REQUIRED
      required:
        - success
        - error
        - code
    PostAccountRef:
      type: object
      properties:
        id:
          type: string
          description: Account id from GET /accounts
      required:
        - id
    PostMediaVideo:
      type: object
      properties:
        type:
          type: string
          enum:
            - video
        url:
          type: string
          format: uri
        duration_sec:
          type: number
        duration_seconds:
          type: number
        duration:
          type: number
        bytes:
          type: number
        size:
          type: number
        width:
          type: integer
        height:
          type: integer
        mime_type:
          type: string
        mime:
          type: string
        cover_url:
          type: string
          format: uri
          description: Optional Instagram Reel cover image URL
      required:
        - type
        - url
    PostMediaSlideshow:
      type: object
      properties:
        type:
          type: string
          enum:
            - slideshow
        urls:
          type: array
          items:
            type: string
            format: uri
          minItems: 1
          maxItems: 35
      required:
        - type
        - urls
    PostSettings:
      type: object
      description: >-
        Optional publish options for Instagram and TikTok only. All fields are
        optional.
      additionalProperties: false
      properties:
        instagram:
          $ref: '#/components/schemas/PostSettingsInstagram'
        tiktok:
          $ref: '#/components/schemas/PostSettingsTiktok'
    PostSettingsInstagram:
      type: object
      description: Instagram-only options (e.g. Reel cover).
      properties:
        cover_url:
          type: string
          format: uri
          description: 'Custom Reel cover image URL (JPG/PNG). Alias: coverUrl.'
    PostSettingsTiktok:
      type: object
      description: >-
        TikTok-only options. Applied to each TikTok account on the post. Query
        creator_info for allowed privacy_level values.
      properties:
        post_mode:
          type: string
          enum:
            - DIRECT_POST
            - MEDIA_UPLOAD
          description: >-
            DIRECT_POST publishes immediately; MEDIA_UPLOAD sends to the TikTok
            inbox for the user to finish in-app.
        privacy_level:
          type: string
          description: >-
            Must match a value from TikTok creator_info (e.g.
            PUBLIC_TO_EVERYONE, SELF_ONLY).
        music_usage_confirmation:
          type: boolean
          description: Confirm third-party music usage rights when required by TikTok.
        disable_comment:
          type: boolean
        disable_duet:
          type: boolean
        disable_stitch:
          type: boolean
        brand_content_toggle:
          type: boolean
          description: Paid partnership promoting a third-party brand.
        brand_organic_toggle:
          type: boolean
          description: Promoting the creator's own business.
        is_aigc:
          type: boolean
          description: AI-generated content disclosure.
        auto_add_music:
          type: boolean
          description: Photo posts only — let TikTok add background music.
        photo_cover_index:
          type: integer
          minimum: 0
          description: Slideshow only — zero-based index of the cover image.
        video_cover_timestamp_ms:
          type: integer
          description: Video reel only — frame timestamp for the cover thumbnail.
    PublicPostSummary:
      type: object
      properties:
        id:
          type: string
        status:
          type: string
          enum:
            - draft
            - scheduled
            - publishing
            - published
            - failed
        type:
          type: string
          enum:
            - video
            - image
            - slideshow
            - text
        caption:
          type: string
        scheduled_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
        accounts:
          type: array
          items:
            type: string
      required:
        - id
        - status
        - type
        - caption
        - scheduled_at
        - accounts
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Clerk session JWT or Unsora API key (uns_live_*)

````