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

# Create Post

> Schedule or publish text-only, video, or slideshow posts to connected accounts.

Schedule or publish immediately to one or more connected accounts.

## Before you post

1. Use an API key for a workspace with an active **Creator, Professional, or Business** subscription.
2. Call [`GET /accounts`](/api-reference/get-accounts) and pick account UUIDs.
3. Check each account's `capabilities.supported_content_kinds` and `caption_limit`.
4. Build the payload below. Only include provider settings for platforms you are actually targeting.

<Warning>
  Videos uploaded through [`POST /files`](/api-reference/upload-file) must be finalized with `POST
      /files/{fileId}/finalize`. Use the finalizer's `data.file.url`, not the upload destination URL, in
  the post payload.
</Warning>

<Tip>
  Omit `media` or set it to `null` only when **every** selected account supports `text_only`.
</Tip>

## Request body

<ParamField body="caption" type="string" required>
  Shared caption text. Final length is checked per targeted account after lookup (hosted accounts:
  500 chars max).
</ParamField>

<ParamField body="accounts" type="array" required>
  One to ten account objects: `[{ "id": "<uuid-from-/accounts>" }]`.
</ParamField>

<ParamField body="media" type="object | null">
  Optional. `video`, `slideshow`, or `null`/omit for text-only posts.

  <Expandable title="Video">
    <ParamField body="type" type="string" required>
      `video`
    </ParamField>

    <ParamField body="url" type="string" required>
      Canonical backend-attested video URL. For Partner uploads, use `data.file.url` returned by
      `POST /files/{fileId}/finalize`; upload destinations and arbitrary remote links are rejected.
    </ParamField>

    <ParamField body="duration_sec" type="number">
      Duration in seconds. Also accepts `duration_seconds`, `duration`, `durationSec`, or
      `video_duration_sec`. **Required** for non-premium X video posts.
    </ParamField>

    <ParamField body="bytes" type="number">
      Optional size in bytes. Also accepts `size`.
    </ParamField>

    <ParamField body="width" type="number">
      Optional width in pixels.
    </ParamField>

    <ParamField body="height" type="number">
      Optional height in pixels.
    </ParamField>

    <ParamField body="mime_type" type="string">
      Optional MIME type. Alias: `mime`.
    </ParamField>

    <ParamField body="metadata" type="object">
      Optional nested object that can repeat `width`, `height`, `mime_type`, and `bytes`.
    </ParamField>
  </Expandable>

  <Expandable title="Slideshow">
    <ParamField body="type" type="string" required>
      `slideshow`
    </ParamField>

    <ParamField body="urls" type="string[]" required>
      1–35 JPG/JPEG/PNG image URLs, in order.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="scheduled_at" type="string">
  ISO 8601 datetime **with timezone offset** (for example `2026-05-21T15:00:00Z`). Omit or send a
  time within 30 seconds of now for immediate publish (`status: pending`). For true scheduled posts,
  the time must be **at least 2 minutes** in the future.
</ParamField>

<ParamField body="external_id" type="string">
  Recommended idempotency key (max 128 chars). Same key + same payload replays the original post
  (`200`, `duplicate: true`). Same key + different payload returns `409 create_failed`. If omitted,
  the API dedupes from a SHA-256 fingerprint of the request body instead; a conflicting replay also
  returns `409 create_failed`.
</ParamField>

<ParamField body="music_url" type="string">
  TikTok-only. A TikTok post URL used to resolve the track. Rejected when any non-TikTok account is
  selected, including Instagram.
</ParamField>

## Provider settings

Use **`settings.<provider>`** for X, Instagram, Facebook, LinkedIn, Bluesky,
and YouTube.

For TikTok and Pinterest on **create**, use the top-level **`tiktok`** and **`pinterest`**
objects — not `settings.tiktok` or `settings.pinterest`. Top-level fields run the full
create-time validation (BYO-only TikTok, hosted TikTok rejection, `MEDIA_UPLOAD` rules, and
field limits).

Only send settings for providers present in your `accounts` list. Unknown `settings` keys return
`422 invalid_payload`.

`capabilities.settings_schema` from [`GET /accounts`](/api-reference/get-accounts) advertises the
public field names accepted for that provider. Use top-level `tiktok`/`pinterest`, or
`settings.<provider>` for the remaining providers. Hosted accounts omit fields their provider
cannot accept. Cross-field and media/account-specific rules are still validated when you create or
update the post.

### YouTube — `settings.youtube`

Requires a **video** post to a YouTube account.

| Field | Type | Required | Notes |
| - | - | - | - |
| `title` | string | No | Max 100 chars. If omitted, derived from `settings.youtube.description`, then `caption` (first non-empty line). |
| `description` | string | No | Overrides `caption` as the YouTube description when set. |
| `type` | string | No | Visibility: `public`, `private`, or `unlisted`. Default `public`. Alias: `privacy`. |
| `privacy` | string | No | Same as `type`. |
| `tags` | array | No | `{ "value": "...", "label": "..." }` items. Publish uses `label` values. Total tag length capped at publish time. |
| `categoryId` | string | No | YouTube category ID. |
| `selfDeclaredMadeForKids` | string | No | `yes` or `no`. Default `no`. Boolean alias: `madeForKids`. |
| `thumbnail` | object | No | Custom thumbnail: `{ "path": "<image-url>" }`. |

### TikTok — top-level `tiktok`

Requires **BYO** TikTok accounts and media (video or slideshow). Hosted TikTok accounts cannot use
TikTok-specific settings.

For **video** posts, the merged publish text built from `caption`, `tiktok.title`, and
`tiktok.description` must stay **≤ 2,200 characters combined**.

| Field | Type | Notes |
| - | - | - |
| `title` | string | Video: max 2,200 chars. Slideshow: max 90 chars. |
| `description` | string | Slideshow: max 4,000 chars. Video: used as the publish body when `caption` is empty. |
| `post_mode` | string | `DIRECT_POST` or `MEDIA_UPLOAD` (slideshow drafts only). Defaults to `DIRECT_POST` at publish time when omitted. |
| `privacy_level` | string | `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, `FOLLOWER_OF_CREATOR`, `SELF_ONLY`. |
| `music_usage_confirmation` | boolean | Confirms the user has accepted TikTok's music-usage terms for this post. Preferred Partner API field. |
| `user_consent` | boolean | Legacy alias for `music_usage_confirmation`. |
| `disable_comment` | boolean | Inverted to allow/deny comments. |
| `disable_duet` | boolean | Video only. |
| `disable_stitch` | boolean | Video only. |
| `auto_add_music` | boolean | Slideshow / photo `DIRECT_POST` only. Omitted defaults to `true` (TikTok auto-picks a soundtrack). `false` still publishes silent. Videos ignore this field. Hosted TikTok ignores it — attach a music URL instead. |
| `is_your_brand` | boolean | Own-brand commercial content. |
| `is_branded_content` | boolean | Third-party promotion. |
| `video_cover_timestamp_ms` | integer | Video `DIRECT_POST` only. Frame offset in milliseconds for the TikTok thumbnail. |

### Pinterest — top-level `pinterest`

Requires at least one Pinterest account and image media.

| Field | Type | Notes |
| - | - | - |
| `board_id` | string | Pinterest board ID. Required for a successful pin publish — no account default is applied. Max 128 chars. |
| `title` | string | Optional pin title. Max 100 chars. |
| `link` | string | Optional destination URL. Max 2,048 chars. |
| `tags` | string\[] | Up to 30 tags, 1–100 chars each. Appended to the pin description; total description must stay ≤ 800 chars. |

### X — `settings.x` or `settings.twitter`

| Field | Type | Required | Notes |
| - | - | - | - |
| `who_can_reply_post` | string | No | `everyone` (default), `following`, `mentionedUsers`, `subscribers`, `verified`. |
| `community` | string | No | X community URL: `https://x.com/i/communities/<id>`. |
| `made_with_ai` | boolean | No | Default `false`. |
| `paid_partnership` | boolean | No | Default `false`. |

### Mastodon — `settings.mastodon`

Only applies to a selected Mastodon account. Create, prepare and ordinary post updates accept the
same native fields; omitted fields retain the existing provider defaults.

| Field | Type | Notes |
| - | - | - |
| `visibility` | string | `public`, `unlisted`, or `private` (followers only). |
| `sensitive` | boolean | Marks the attached media as sensitive. |
| `spoilerText` | string | Content warning, up to 500 characters. |

### Instagram — `settings.instagram` or `settings.instagram-standalone`

Both keys map to the same stored settings. If both are sent, `instagram-standalone` wins.

| Field | Type | Required | Notes |
| - | - | - | - |
| `post_type` | string | Recommended | `post` (feed/reel) or `story`. |
| `is_trial_reel` | boolean | No | Trial reel flag for video posts. |
| `graduation_strategy` | string | No | `MANUAL` or `SS_PERFORMANCE` when using trial reels. |
| `collaborators` | array | No | `[{ "label": "<username>" }]`. Ignored for stories. |
| `cover_url` | string | No | Custom Reel cover image URL. Takes priority over `thumb_offset_ms`. Video `post_type: post` only. Must be publicly accessible. **BYO Instagram only** — hosted accounts reject cover settings. |
| `thumb_offset_ms` | integer | No | Reel thumbnail frame offset in milliseconds when `cover_url` is omitted. Video `post_type: post` only. **BYO Instagram only.** |
| `share_to_feed` | boolean | No | Share the Reel to the main feed. Default `true`. Set `false` for Reels tab only. **BYO Instagram only.** |

### Facebook — `settings.facebook`

| Field | Type | Required | Notes |
| - | - | - | - |
| `url` | string | No | Optional link attachment URL on the post. |

### LinkedIn — `settings.linkedin` or `settings.linkedin-page`

Both keys map to the same stored settings. If both are sent, `linkedin-page` wins.

| Field | Type | Required | Notes |
| - | - | - | - |
| `post_as_images_carousel` | boolean | No | Post multi-image content as a LinkedIn carousel/PDF. |
| `carousel_name` | string | No | Carousel/PDF title. Default `slides`. |

### Bluesky — `settings.bluesky`

No provider-specific fields. Send `{}` or omit.

## Settings key reference

| Request key | Alias | Stored as |
| - | - | - |
| `settings.x` | `settings.twitter` | `xSettings` |
| `settings.instagram-standalone` | `settings.instagram` | `instagramSettings` |
| `settings.linkedin-page` | `settings.linkedin` | `linkedinSettings` |
| Top-level `tiktok` | — | `tikTokSettings` |
| Top-level `pinterest` | — | `pinterestSettings` |

## Limits (quick reference)

* **Video:** MP4/MOV/M4V/AVI, under 100MB. Hosted accounts: 15–60s when duration is provided.
* **Non-premium X video:** duration metadata required; max 140 seconds.
* **Slideshow:** 1–35 images, each under 5MB.
* **Caption caps (BYO):** X 280 (4,000 for premium accounts); Instagram 2,200; TikTok video 2,200 / photo description 4,000; LinkedIn 3,000; Pinterest 800; YouTube 5,000 bytes; Facebook 63,206.

## Response

Fresh creates return `201`. Idempotent replays return `200`.

```json theme={null}
{
  "ok": true,
  "code": 201,
  "message": "Post scheduled",
  "data": {
    "id": "11111111-1111-1111-1111-111111111111",
    "status": "scheduled",
    "scheduled_at": "2026-05-21T15:00:00Z",
    "warnings": []
  }
}
```

`duplicate: true` appears on idempotent replays. `warnings` are non-blocking (for example missing
video duration metadata).

## Examples

<RequestExample>
  ```bash Video to YouTube theme={null}
  curl --request POST \
    --url https://www.genviral.io/api/partner/v1/posts \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
    "caption": "Weekly product recap and what shipped this sprint.",
    "media": {
      "type": "video",
      "url": "https://cdn.example.com/recap.mp4",
      "duration_sec": 42
    },
    "accounts": [{ "id": "YOUR_YOUTUBE_ACCOUNT_ID" }],
    "settings": {
      "youtube": {
        "title": "Weekly product recap",
        "type": "public"
      }
    },
    "external_id": "yt-recap-2026-05-21"
  }'
  ```

  ```bash TikTok slideshow draft theme={null}
  curl --request POST \
    --url https://www.genviral.io/api/partner/v1/posts \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
    "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_BYO_ACCOUNT_ID" }],
    "tiktok": {
      "post_mode": "MEDIA_UPLOAD",
      "music_usage_confirmation": true,
      "privacy_level": "SELF_ONLY"
    },
    "external_id": "tiktok-draft-001"
  }'
  ```

  ```bash Pinterest pin theme={null}
  curl --request POST \
    --url https://www.genviral.io/api/partner/v1/posts \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
    "caption": "Minimal kitchen organization ideas that actually stick.",
    "media": {
      "type": "slideshow",
      "urls": ["https://cdn.example.com/pin-cover.jpg"]
    },
    "accounts": [{ "id": "YOUR_PINTEREST_ACCOUNT_ID" }],
    "pinterest": {
      "board_id": "YOUR_BOARD_ID",
      "link": "https://example.com/kitchen-guide",
      "tags": ["kitchen organization", "home tips"]
    },
    "external_id": "pinterest-pin-001"
  }'
  ```

  ```bash Instagram Reel cover theme={null}
  curl --request POST \
    --url https://www.genviral.io/api/partner/v1/posts \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
    "caption": "Behind the scenes from today'\''s shoot.",
    "media": {
      "type": "video",
      "url": "https://cdn.example.com/reel.mp4"
    },
    "accounts": [{ "id": "YOUR_INSTAGRAM_BYO_ACCOUNT_ID" }],
    "settings": {
      "instagram": {
        "post_type": "post",
        "cover_url": "https://cdn.example.com/reel-cover.jpg",
        "share_to_feed": false
      }
    },
    "external_id": "instagram-reel-cover-001"
  }'
  ```

  ```bash Text-only X post theme={null}
  curl --request POST \
    --url https://www.genviral.io/api/partner/v1/posts \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
    "caption": "Ship the repeatable thing, not the busywork.",
    "accounts": [{ "id": "YOUR_X_ACCOUNT_ID" }],
    "media": null,
    "settings": {
      "x": {
        "who_can_reply_post": "everyone"
      }
    }
  }'
  ```
</RequestExample>

## Errors

| Code | When |
| - | - |
| `422 invalid_payload` | Schema validation failed (includes a `fields` array naming each offending path) |
| `400 invalid_json` | Request body is not valid JSON |
| `400 validation_failed` | Caption, media, music, TikTok/Pinterest, or schedule rules failed |
| `400 unknown_accounts` | Account ID outside key scope |
| `400 media_unreachable` | Media URL not reachable |
| `400 video_verification_required` | Video must complete `POST /files` with a stable idempotency key → presigned PUT → `POST /files/{fileId}/finalize` with matching metadata and another stable key; retry with `data.file.url` |
| `400 invalid_music_url` | Invalid or non-TikTok music URL |
| `401` | Missing or invalid API key |
| `402 subscription_required` | No active Creator, Professional, or Business subscription |
| `402 insufficient_credits` | Buy a credit pack at `https://www.genviral.io/billing?tab=credits`, then retry |
| `403 subscription_required` | Hosted TikTok posting without an active TikTok virtual subscription |
| `403 tier_not_allowed` | Scheduler tier blocked |
| `409 create_failed` | Same `external_id` or dedupe fingerprint reused with a different create payload |
| `500 create_failed` | Unexpected upstream failure |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.