# CLI Posting Commands

Create, schedule, publish, update, and delete posts from the command line. All
commands honor the [global flags](/cli#global-flags) and print JSON with `--json`.

## Commands

| Command | Description |
|---------|-------------|
| `posts:list` | List posts. Flags: `--status <draft\|in_approval\|scheduled\|published\|failed\|rejected>`, `--limit`, `--offset`. A post that waits for approval or was rejected prints an `Approval:` line that names `posts:approval <id>` |
| `posts:get <id>` | Full details for one post |
| `posts:create` | Create a draft or scheduled post. `--text` is required |
| `posts:create-and-publish` | Create a post and publish it immediately (no `--schedule`) |
| `posts:update <id>` | Update a draft or scheduled post; takes the same flags as `posts:create` |
| `posts:publish <id>` | Publish an existing draft or scheduled post now |
| `posts:retry <id>` | Retry only the failed platforms of a `failed` or `warning` post; succeeded channels are never re-posted (max 3 retries per platform) |
| `posts:approve <id>` | Approve the current step of a post's approval workflow (`approval_status: "pending"`). You must be a listed approver for that step; steps approve in order |
| `posts:reject <id>` | Reject a post's approval workflow, stopping it immediately (not just the current step). Flag: `--comment "..."` (optional, shown to the requester and other approvers) |
| `posts:approval <id>` | Read the review of a post that went through an approval workflow: the step it waits on and who still has to decide, who rejected it and why, every step with each approver's decision, and the comment thread. Read-only; `--json` returns the full [review object](/creating-posts#reading-the-review) |
| `posts:delete <id>` | Delete a post (irreversible) |
| `posts:recent-platform` | Fetch recent posts live from the connected platforms, including content published outside OmniSocials. Flags: `--limit` (1-50, default 25; X defaults to 10 unless set explicitly), `--platforms <a,b>`. X results may be served from a snapshot up to 24h old (X bills per returned post); it refreshes right after you publish to X through OmniSocials. Requires the `analytics:read` scope |

## Post options

For `posts:create`, `posts:create-and-publish`, and `posts:update`:

| Flag | Description |
|------|-------------|
| `--text <str>` | Caption / post content (required on create) |
| `--channels <a,b>` | Comma-separated [channel ids](/cli#channels) |
| `--type <post\|story\|reel>` | Content type (default `post`) |
| `--schedule <ISO8601>` | Schedule time, e.g. `2026-08-01T09:00:00Z` |
| `--draft` | With `--schedule`: store a planned draft that keeps the time but is not scheduled (a person confirms it in the dashboard, or `posts:update <id> --no-draft`). On `posts:update` alone, `--draft` unschedules a scheduled post and keeps its time |
| `--no-draft` | `posts:update` only: schedule a planned draft at the time it already carries |
| `--media-urls <url,url>` | Attach media by URL |
| `--media-ids <id,id>` | Attach media from the library |
| `--media-fit <ch=action,...>` | What happens to a feed image outside what a channel shows uncropped, per channel: `instagram=keep,pinterest=crop`. Actions `auto` (default), `crop`, `keep`. Without it Instagram crops a 9:16 image to 4:5 around the subject and Pinterest keeps a Pin taller than 2:3; every decision is printed as a warning after the post is created. On `posts:update` alone, re-decides the stored images (`keep` gives the original back). See [Image fit per channel](/creating-posts#image-fit-per-channel) |
| `--link-url <url>` | Link-preview card (LinkedIn / Facebook) |
| `--link-title <str>` | Override the link-preview title |
| `--link-description <str>` | Override the link-preview description |
| `--link-thumbnail-url <url>` | Override the link-preview thumbnail |
| `--location-id <id>` | Instagram place tag (find ids with [`locations:search`](/cli/platforms#location-search)) |
| `--collaborators <a,b>` | Instagram co-author usernames (max 3) |
| `--user-tags '<json>'` | Instagram photo tags: JSON array of `{"username","x","y","image_index?"}` |
| `--x-thread "a \|\| b \|\| c"` | X thread; parts split on `\|\|` |
| `--bluesky-thread "a \|\| b \|\| c"` | Bluesky thread |
| `--mastodon-thread "a \|\| b \|\| c"` | Mastodon thread |
| `--threads-thread "a \|\| b \|\| c"` | Threads thread (Meta Threads) |
| `--video-cover-json '<json>'` | Thumbnail for a post whose media is one video: `{"type":"frame","thumb_offset":3000}` (milliseconds into the video) or `{"type":"custom","cover_url":"https://..."}`, plus optional `"overrides"` keyed by platform. Applied on Instagram, Facebook, LinkedIn, TikTok (frame only), Pinterest and YouTube Shorts (channel-dependent, see below). The literal value `null` removes it on update. See [Creating reels](/creating-reels) |
| `--threads-location-id <id>` | Threads place tag, rolling out (find ids with [`locations:search --platform threads`](/cli/platforms#location-search); the literal value `null` clears the tag on update) |

## Platform-specific flags

### Instagram

| Flag | Description |
|------|-------------|
| `--instagram-share-to-feed` | Also share the reel to the feed |
| `--instagram-cover-url` | Custom reel thumbnail URL |
| `--instagram-thumbnail-type` | `from-video` or `from-library` |
| `--instagram-thumb-offset` | Video timestamp in ms (for `from-video`) |
| `--instagram-audio-id` | Licensed music for reels (find ids with [`audio:search`](/cli/platforms#audio-search)) |
| `--instagram-audio-volume` | Music volume 0-100 |
| `--instagram-video-volume` | Original audio volume 0-100 (0 = music only) |
| `--instagram-first-comment` | First comment (not for stories) |
| `--instagram-trial-reel` | Publish as a trial reel (roughly 1,000+ followers required) |
| `--instagram-trial-graduation-strategy` | `MANUAL` or `SS_PERFORMANCE` |

### YouTube

| Flag | Description |
|------|-------------|
| `--youtube-title` | Video title |
| `--youtube-privacy` | `public`, `private`, or `unlisted` |
| `--youtube-tags` | Comma-separated tags |
| `--youtube-category-id` | Category ID |
| `--youtube-made-for-kids` | Mark as made for kids |
| `--youtube-notify-subscribers` | Notify subscribers on publish |
| `--youtube-contains-synthetic-media` | Mark as AI-generated |
| `--youtube-first-comment` | First comment (video must allow comments) |

A custom thumbnail (`--video-cover-json`) shows on a Short only on channels where YouTube has enabled custom Shorts thumbnails (Partner Program channels first). Other channels show a frame from the video.

### TikTok

| Flag | Description |
|------|-------------|
| `--tiktok-privacy` | `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, `FOLLOWER_OF_CREATOR`, `SELF_ONLY` |
| `--tiktok-disable-comment` | Disable comments |
| `--tiktok-disable-duet` | Disable duets |
| `--tiktok-disable-stitch` | Disable stitches |
| `--tiktok-video-cover-timestamp-ms` | Cover frame timestamp in ms |
| `--tiktok-is-aigc` | Mark as AI-generated content |
| `--tiktok-brand-content-toggle` | Paid partnership disclosure |
| `--tiktok-brand-organic-toggle` | Own-brand disclosure |
| `--tiktok-auto-add-music` | Auto-add music to photo carousels |

### Pinterest

| Flag | Description |
|------|-------------|
| `--pinterest-board-id` | Board ID (required for Pinterest; find it with `accounts:get`) |
| `--pinterest-title` | Pin title |
| `--pinterest-link` | Destination URL |
| `--pinterest-video-cover` | Cover image URL for video pins |
| `--pinterest-alt-text` | Accessibility alt text |
| `--pinterest-product-tags` | Products to tag on the Pin: comma-separated Pin IDs or Pin links of your own product Pins, max 24 (find ids with [`pinterest:products`](/cli/platforms#pinterest-products)) |

Product tags are added right after the Pin is published. Pinterest accepts a product only when its Pin is public, belongs to the same Pinterest account and links to a website that account claimed. A product that Pinterest refuses never fails the post. After publishing, `posts:list` prints the outcome as `Pinterest products: 1 of 2 tagged (skipped: <pin_id> PIN_IS_PRIVATE)`, and `posts:get` returns it as `pinterest.product_tags_result`. On `posts:update` the Pinterest flags replace the stored Pinterest options as one object, so pass them all again; leaving `--pinterest-product-tags` out removes the tags.

### Other platforms

| Flag | Description |
|------|-------------|
| `--facebook-first-comment` | First comment on the page post |
| `--linkedin-first-comment` | First comment on a personal profile post |
| `--linkedin-page-first-comment` | First comment on a company page post |
| `--linkedin-carousel-as-images` | `true` = publish 2+ images as a plain gallery instead of the default document carousel (profile post) |
| `--linkedin-page-carousel-as-images` | Same for the company page post |
| `--linkedin-document-source` | `original_pdf` (default) or `slides`. When the images are the unchanged pages of one uploaded PDF, the default sends that original file to LinkedIn; `slides` rebuilds the document from the slide images (profile post) |
| `--linkedin-page-document-source` | Same for the company page post |
| `--x-reply-settings` | `following` or `mentionedUsers` (empty = everyone) |

## Examples

Schedule an image post to Instagram and LinkedIn:

```bash
omnisocials posts:create \
  --text "Launch day! Here is what we built." \
  --channels instagram,linkedin \
  --media-urls https://example.com/launch.jpg \
  --schedule 2026-08-01T09:00:00Z
```

Publish an X thread right away:

```bash
omnisocials posts:create-and-publish \
  --channels x \
  --text "How we cut our posting time by 90%" \
  --x-thread "How we cut our posting time by 90% || Step 1: stop copy-pasting between tabs || Step 2: automate the boring parts"
```

Publish a reel with licensed music:

```bash
omnisocials audio:search "summer vibes"
omnisocials posts:create-and-publish \
  --type reel \
  --channels instagram \
  --text "POV: your content calendar fills itself" \
  --media-urls https://example.com/reel.mp4 \
  --instagram-audio-id 123456789 \
  --instagram-video-volume 0
```

Tag products on a Pinterest Pin:

```bash
omnisocials pinterest:products
omnisocials posts:create \
  --text "Autumn outfit" \
  --channels pinterest \
  --media-urls https://example.com/outfit.jpg \
  --pinterest-board-id 1234567890 \
  --pinterest-product-tags 813744226420795884,813744226420795885
```

Find out why a post was rejected, or who it waits for:

```bash
omnisocials posts:list --status rejected
omnisocials posts:approval 123456
```

```text
Approval review for post 123456
────────────────────────────────────────
Status: rejected
Workflow: Content approval (ID: 42)
Requested by: Alex at 2026-10-01T09:00:00.000Z
Rejected by: Jordan on step 2 at 2026-10-01T14:30:00.000Z
Reason: The image does not match the caption

Steps
  Step 1: Team review [any one approves]: approved
    Sam: approved at 2026-10-01T10:15:00.000Z
  Step 2: Client approval [all must approve]: rejected
    Jordan: rejected at 2026-10-01T14:30:00.000Z ("The image does not match the caption")

Comments (1, oldest first)
  2026-10-01T14:28:00.000Z  Jordan [instagram]: Please use the new logo
```

While a post is still in review, the same command prints a `Waiting on:` line with the current step and the approvers who still have to decide.

Check what went out recently, straight from the platforms:

```bash
omnisocials posts:recent-platform --platforms instagram,tiktok --limit 10
```
